From 1013d74f13f27c53ae0abcb1a1a9072b1afd3a77 Mon Sep 17 00:00:00 2001 From: Jesse David Peterson Date: Tue, 9 Dec 2025 13:35:43 -0500 Subject: [PATCH 1/6] TimeRange: Additional keyboard shortcut `t =` to complement `t +` for zoom in (#115022) feat(time-range): additional keyboard shortcut "t =" for zoom in --- public/app/core/services/keybindingSrv.ts | 4 +++ .../scene/keyboardShortcuts.test.ts | 31 +++++++++++++++++++ .../scene/keyboardShortcuts.ts | 7 +++++ 3 files changed, 42 insertions(+) diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index ea232baffcc..2cbda7827e3 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -236,6 +236,10 @@ export class KeybindingSrv { appEvents.publish(new ZoomOutEvent({ scale: 0.5, updateUrl })); }); + this.bind('t =', () => { + appEvents.publish(new ZoomOutEvent({ scale: 0.5, updateUrl })); + }); + this.bind('t -', () => { appEvents.publish(new ZoomOutEvent({ scale: 2, updateUrl })); }); diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.test.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.test.ts index 9a70858f39b..8772d49f138 100644 --- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.test.ts +++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.test.ts @@ -269,6 +269,13 @@ describe('setupKeyboardShortcuts', () => { expect(tPlusBinding).toBeDefined(); }); + it('should setup t = zoom in shortcut', () => { + setupKeyboardShortcuts(mockScene); + + const tEqualsBinding = mockKeybindingSet.addBinding.mock.calls.find((call) => call[0].key === 't ='); + expect(tEqualsBinding).toBeDefined(); + }); + it('should setup t - zoom out shortcut with keypress type', () => { setupKeyboardShortcuts(mockScene); @@ -302,9 +309,11 @@ describe('setupKeyboardShortcuts', () => { setupKeyboardShortcuts(mockScene); const tPlusBinding = mockKeybindingSet.addBinding.mock.calls.find((call) => call[0].key === 't +'); + const tEqualsBinding = mockKeybindingSet.addBinding.mock.calls.find((call) => call[0].key === 't ='); const tMinusBinding = mockKeybindingSet.addBinding.mock.calls.find((call) => call[0].key === 't -'); expect(tPlusBinding).toBeUndefined(); + expect(tEqualsBinding).toBeUndefined(); expect(tMinusBinding).toBeUndefined(); }); }); @@ -364,6 +373,28 @@ describe('setupKeyboardShortcuts', () => { expect(newSpan).toBe(3 * 60 * 60 * 1000); // 3 hours in milliseconds }); + it('should zoom in (scale 0.5) when t = is pressed', () => { + setupKeyboardShortcuts(mockScene); + + const tEqualsBinding = mockKeybindingSet.addBinding.mock.calls.find((call) => call[0].key === 't ='); + const handler = tEqualsBinding![0].onTrigger; + + handler(); + + // Scale 0.5 should result in 3 hour span (half of 6) + expect(mockTimeRange.onTimeRangeChange).toHaveBeenCalledWith( + expect.objectContaining({ + from: expect.any(Object), + to: expect.any(Object), + raw: expect.any(Object), + }) + ); + + const call = mockTimeRange.onTimeRangeChange.mock.calls[0][0]; + const newSpan = call.to.valueOf() - call.from.valueOf(); + expect(newSpan).toBe(3 * 60 * 60 * 1000); // 3 hours in milliseconds + }); + it('should keep center point when zooming in', () => { setupKeyboardShortcuts(mockScene); diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts index 1113b8a8a8c..f1878aeb466 100644 --- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts +++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts @@ -138,6 +138,13 @@ export function setupKeyboardShortcuts(scene: DashboardScene) { }, }); + keybindings.addBinding({ + key: 't =', + onTrigger: () => { + handleZoom(scene, 0.5); + }, + }); + keybindings.addBinding({ key: 't -', type: 'keypress', // NOTE: Because some browsers/OS identify minus symbol differently. From b8acfade2194fb33f430f7fe39f1015a9a160858 Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Tue, 9 Dec 2025 12:59:46 -0600 Subject: [PATCH 2/6] Unified Storage: Adds debug logs for checking quotas (#115036) * adds debug logs for checking quotas * make update-workspace --- apps/iam/go.sum | 10 ++++++---- go.mod | 2 ++ go.sum | 4 ++++ pkg/storage/unified/resource/server.go | 7 +++++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 7f2fd8f462f..0b22bce3774 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -499,8 +499,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= @@ -853,6 +853,8 @@ github.com/grafana/grafana-plugin-sdk-go v0.284.0 h1:1bK7eWsnPBLUWDcWJWe218Ik5ad github.com/grafana/grafana-plugin-sdk-go v0.284.0/go.mod h1:lHPniaSxq3SL5MxDIPy04TYB1jnTp/ivkYO+xn5Rz3E= github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b h1:6Bo65etvjQ4tStkaA5+N3A3ENbO4UAWj53TxF6g2Hdk= github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b/go.mod h1:6+wASOCN8LWt6FJ8dc0oODUBIEY5XHaE6ABi8g0mR+k= +github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2 h1:rDPMdshj3QMvpXn+wK4T8awF9n2sd8i4YRiGqX2xTvg= +github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2/go.mod h1:M7bV60iRB61y0ISPG1HX/oNLZtlh0ZF22rUYwNkAKjo= github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0= github.com/grafana/grafana/pkg/promlib v0.0.8/go.mod h1:U1ezG/MGaEPoThqsr3lymMPN5yIPdVTJnDZ+wcXT+ao= github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 h1:A65jWgLk4Re28gIuZcpC0aTh71JZ0ey89hKGE9h543s= @@ -1416,8 +1418,8 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3 h1:/4/IJi5iyTdh6mqOUaASW148HQpujYiHl0Wl78dSOSc= github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3/go.mod h1:aJIMhRsunltJR926EB2MUg8qHemFQDreSB33pyto2Ps= -github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= -github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= +github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= diff --git a/go.mod b/go.mod index af798993f31..89590cb3a07 100644 --- a/go.mod +++ b/go.mod @@ -659,6 +659,8 @@ require ( require github.com/grafana/tempo v1.5.1-0.20250529124718-87c2dc380cec // @grafana/observability-traces-and-profiling +require github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2 + require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/IBM/pgxpoolprometheus v1.1.2 // indirect diff --git a/go.sum b/go.sum index b08b87734c7..f4f91335d2b 100644 --- a/go.sum +++ b/go.sum @@ -646,6 +646,7 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0p github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2 h1:qFYgLH2zZe3WHpQgUrzeazC+ebDebwAQqS9yE1cP5Bs= github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2/go.mod h1:09/ALd1AXCTCOfcJYD8+jIYKmFmi6PVCkTsipC18F7E= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -1074,6 +1075,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3 github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= @@ -1651,6 +1653,8 @@ github.com/grafana/grafana-plugin-sdk-go v0.284.0 h1:1bK7eWsnPBLUWDcWJWe218Ik5ad github.com/grafana/grafana-plugin-sdk-go v0.284.0/go.mod h1:lHPniaSxq3SL5MxDIPy04TYB1jnTp/ivkYO+xn5Rz3E= github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b h1:6Bo65etvjQ4tStkaA5+N3A3ENbO4UAWj53TxF6g2Hdk= github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b/go.mod h1:6+wASOCN8LWt6FJ8dc0oODUBIEY5XHaE6ABi8g0mR+k= +github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2 h1:rDPMdshj3QMvpXn+wK4T8awF9n2sd8i4YRiGqX2xTvg= +github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2/go.mod h1:M7bV60iRB61y0ISPG1HX/oNLZtlh0ZF22rUYwNkAKjo= github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0= github.com/grafana/grafana/pkg/promlib v0.0.8/go.mod h1:U1ezG/MGaEPoThqsr3lymMPN5yIPdVTJnDZ+wcXT+ao= github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 h1:A65jWgLk4Re28gIuZcpC0aTh71JZ0ey89hKGE9h543s= diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index bdfb2e8c7ca..c4052938603 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -1618,6 +1618,7 @@ func (s *server) checkQuota(ctx context.Context, nsr NamespacedResource) { )) if s.overridesService == nil { + s.log.FromContext(ctx).Debug("overrides service not configured, skipping quota check", "namespace", nsr.Namespace, "group", nsr.Group, "resource", nsr.Resource) return } @@ -1632,6 +1633,12 @@ func (s *server) checkQuota(ctx context.Context, nsr NamespacedResource) { s.log.FromContext(ctx).Error("failed to get resource stats for quota checking", "namespace", nsr.Namespace, "group", nsr.Group, "resource", nsr.Resource, "error", err) return } + if len(stats) > 0 { + s.log.FromContext(ctx).Debug("stats found", "namespace", nsr.Namespace, "group", nsr.Group, "resource", nsr.Resource, "count", stats[0].Count) + } else { + s.log.FromContext(ctx).Debug("no stats found for resource", "namespace", nsr.Namespace, "group", nsr.Group, "resource", nsr.Resource) + } + if len(stats) > 0 && stats[0].Count >= int64(quota.Limit) { s.log.FromContext(ctx).Info("Quota exceeded on create", "namespace", nsr.Namespace, "group", nsr.Group, "resource", nsr.Resource, "quota", quota.Limit, "count", stats[0].Count, "stats_resource", stats[0].Resource) } From b3980eeec8214055116e99f078646f7a56b509ad Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Tue, 9 Dec 2025 21:46:18 +0200 Subject: [PATCH 3/6] IAM: Add tracing for legacy stores (#114974) --- pkg/registry/apis/iam/register.go | 12 +++++------ pkg/registry/apis/iam/serviceaccount/store.go | 18 ++++++++++++++-- pkg/registry/apis/iam/sso/store.go | 18 ++++++++++++++-- pkg/registry/apis/iam/team/store.go | 21 +++++++++++++++++-- pkg/registry/apis/iam/teambinding/store.go | 21 +++++++++++++++++-- pkg/registry/apis/iam/user/legacy_search.go | 8 ++++++- .../apis/iam/user/legacy_search_test.go | 7 ++++--- pkg/registry/apis/iam/user/store.go | 21 +++++++++++++++++-- 8 files changed, 106 insertions(+), 20 deletions(-) diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index b9da9e6f2c5..85d8bc4e4f1 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -90,11 +90,11 @@ func RegisterAPIService( builder := &IdentityAccessManagementAPIBuilder{ store: store, - userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation), - saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation), - legacyTeamStore: team.NewLegacyStore(store, legacyAccessClient, enableAuthnMutation), - teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation), - ssoLegacyStore: sso.NewLegacyStore(ssoService), + userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing), + saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation, tracing), + legacyTeamStore: team.NewLegacyStore(store, legacyAccessClient, enableAuthnMutation, tracing), + teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation, tracing), + ssoLegacyStore: sso.NewLegacyStore(ssoService, tracing), coreRolesStorage: coreRolesStorage, rolesStorage: rolesStorage, resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider), @@ -114,7 +114,7 @@ func RegisterAPIService( dual: dual, unified: unified, userSearchClient: resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(), - unified, user.NewUserLegacySearchClient(userService), features), + unified, user.NewUserLegacySearchClient(userService, tracing), features), teamSearch: NewTeamSearchHandler(tracing, dual, team.NewLegacyTeamSearchClient(teamService), unified, features), } apiregistration.RegisterAPI(builder) diff --git a/pkg/registry/apis/iam/serviceaccount/store.go b/pkg/registry/apis/iam/serviceaccount/store.go index 2980322cb27..db4709a4e2e 100644 --- a/pkg/registry/apis/iam/serviceaccount/store.go +++ b/pkg/registry/apis/iam/serviceaccount/store.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,14 +36,15 @@ var ( var resource = iamv0alpha1.ServiceAccountResourceInfo -func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient, enableAuthnMutation bool) *LegacyStore { - return &LegacyStore{store, ac, enableAuthnMutation} +func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient, enableAuthnMutation bool, tracer trace.Tracer) *LegacyStore { + return &LegacyStore{store, ac, enableAuthnMutation, tracer} } type LegacyStore struct { store legacy.LegacyIdentityStore ac claims.AccessClient enableAuthnMutation bool + tracer trace.Tracer } // DeleteCollection implements rest.CollectionDeleter. @@ -52,6 +54,9 @@ func (s *LegacyStore) DeleteCollection(ctx context.Context, deleteValidation res // Delete implements rest.GracefulDeleter. func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + ctx, span := s.tracer.Start(ctx, "serviceaccount.Delete") + defer span.End() + if !s.enableAuthnMutation { return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "delete") } @@ -95,6 +100,9 @@ func (s *LegacyStore) Update(ctx context.Context, name string, objInfo rest.Upda // Create implements rest.Creater. func (s *LegacyStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "serviceaccount.Create") + defer span.End() + if !s.enableAuthnMutation { return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "create") } @@ -165,6 +173,9 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, } func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "serviceaccount.List") + defer span.End() + res, err := common.List( ctx, resource, s.ac, common.PaginationFromListOptions(options), func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0alpha1.ServiceAccount], error) { @@ -228,6 +239,9 @@ func extractPluginNameFromTitle(title string) string { } func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "serviceaccount.Get") + defer span.End() + ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err diff --git a/pkg/registry/apis/iam/sso/store.go b/pkg/registry/apis/iam/sso/store.go index c9965adc152..9ab09fa6ef4 100644 --- a/pkg/registry/apis/iam/sso/store.go +++ b/pkg/registry/apis/iam/sso/store.go @@ -7,6 +7,7 @@ import ( commonv1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" + "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,12 +33,13 @@ var ( var resource = iamv0.SSOSettingResourceInfo -func NewLegacyStore(service ssosettings.Service) *LegacyStore { - return &LegacyStore{service} +func NewLegacyStore(service ssosettings.Service, tracer trace.Tracer) *LegacyStore { + return &LegacyStore{service, tracer} } type LegacyStore struct { service ssosettings.Service + tracer trace.Tracer } // Destroy implements rest.Storage. @@ -71,6 +73,9 @@ func (s *LegacyStore) NewList() runtime.Object { // List implements rest.Lister. func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "sso.List") + defer span.End() + ns, _ := request.NamespaceInfoFrom(ctx, false) settings, err := s.service.List(ctx) @@ -88,6 +93,9 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt // Get implements rest.Getter. func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "sso.Get") + defer span.End() + ns, _ := request.NamespaceInfoFrom(ctx, false) setting, err := s.service.GetForProviderWithRedactedSecrets(ctx, name) @@ -112,6 +120,9 @@ func (s *LegacyStore) Update( _ bool, _ *metav1.UpdateOptions, ) (runtime.Object, bool, error) { + ctx, span := s.tracer.Start(ctx, "sso.Update") + defer span.End() + const created = false ident, err := identity.GetRequester(ctx) if err != nil { @@ -148,6 +159,9 @@ func (s *LegacyStore) Delete( _ rest.ValidateObjectFunc, options *metav1.DeleteOptions, ) (runtime.Object, bool, error) { + ctx, span := s.tracer.Start(ctx, "sso.Delete") + defer span.End() + obj, err := s.Get(ctx, name, nil) if err != nil { return obj, false, err diff --git a/pkg/registry/apis/iam/team/store.go b/pkg/registry/apis/iam/team/store.go index e048fdb7a6d..c667c8ec374 100644 --- a/pkg/registry/apis/iam/team/store.go +++ b/pkg/registry/apis/iam/team/store.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" + "go.opentelemetry.io/otel/trace" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -35,14 +36,15 @@ var ( var resource = iamv0alpha1.TeamResourceInfo -func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient, enableAuthnMutation bool) *LegacyStore { - return &LegacyStore{store, ac, enableAuthnMutation} +func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient, enableAuthnMutation bool, tracer trace.Tracer) *LegacyStore { + return &LegacyStore{store, ac, enableAuthnMutation, tracer} } type LegacyStore struct { store legacy.LegacyIdentityStore ac claims.AccessClient enableAuthnMutation bool + tracer trace.Tracer } func (s *LegacyStore) New() runtime.Object { @@ -74,6 +76,9 @@ func (s *LegacyStore) DeleteCollection(ctx context.Context, deleteValidation res // Delete implements rest.GracefulDeleter. func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + ctx, span := s.tracer.Start(ctx, "team.Delete") + defer span.End() + if !s.enableAuthnMutation { return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "delete") } @@ -112,6 +117,9 @@ func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation // Update implements rest.Updater. func (s *LegacyStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + ctx, span := s.tracer.Start(ctx, "team.Update") + defer span.End() + if !s.enableAuthnMutation { return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "update") } @@ -161,6 +169,9 @@ func (s *LegacyStore) Update(ctx context.Context, name string, objInfo rest.Upda } func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "team.List") + defer span.End() + res, err := common.List( ctx, resource, s.ac, common.PaginationFromListOptions(options), func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0alpha1.Team], error) { @@ -197,6 +208,9 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt } func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "team.Get") + defer span.End() + ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -219,6 +233,9 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO } func (s *LegacyStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "team.Create") + defer span.End() + if !s.enableAuthnMutation { return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "create") } diff --git a/pkg/registry/apis/iam/teambinding/store.go b/pkg/registry/apis/iam/teambinding/store.go index eaaa4766e39..6f6fd4ce162 100644 --- a/pkg/registry/apis/iam/teambinding/store.go +++ b/pkg/registry/apis/iam/teambinding/store.go @@ -6,6 +6,7 @@ import ( "strconv" "time" + "go.opentelemetry.io/otel/trace" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,13 +36,14 @@ var ( _ rest.CollectionDeleter = (*LegacyBindingStore)(nil) ) -func NewLegacyBindingStore(store legacy.LegacyIdentityStore, enableAuthnMutation bool) *LegacyBindingStore { - return &LegacyBindingStore{store, enableAuthnMutation} +func NewLegacyBindingStore(store legacy.LegacyIdentityStore, enableAuthnMutation bool, tracer trace.Tracer) *LegacyBindingStore { + return &LegacyBindingStore{store, enableAuthnMutation, tracer} } type LegacyBindingStore struct { store legacy.LegacyIdentityStore enableAuthnMutation bool + tracer trace.Tracer } // Destroy implements rest.Storage. @@ -73,6 +75,9 @@ func (l *LegacyBindingStore) ConvertToTable(ctx context.Context, object runtime. } func (l *LegacyBindingStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + ctx, span := l.tracer.Start(ctx, "teambinding.Update") + defer span.End() + if !l.enableAuthnMutation { return nil, false, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "update") } @@ -125,6 +130,9 @@ func (l *LegacyBindingStore) Update(ctx context.Context, name string, objInfo re } func (l *LegacyBindingStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + ctx, span := l.tracer.Start(ctx, "teambinding.Delete") + defer span.End() + if !l.enableAuthnMutation { return nil, false, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "delete") } @@ -160,6 +168,9 @@ func (l *LegacyBindingStore) DeleteCollection(ctx context.Context, deleteValidat } func (l *LegacyBindingStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + ctx, span := l.tracer.Start(ctx, "teambinding.Create") + defer span.End() + if !l.enableAuthnMutation { return nil, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "create") } @@ -230,6 +241,9 @@ func (l *LegacyBindingStore) Create(ctx context.Context, obj runtime.Object, cre // Get implements rest.Getter. func (l *LegacyBindingStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ctx, span := l.tracer.Start(ctx, "teambinding.Get") + defer span.End() + ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -254,6 +268,9 @@ func (l *LegacyBindingStore) Get(ctx context.Context, name string, options *meta // List implements rest.Lister. func (l *LegacyBindingStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + ctx, span := l.tracer.Start(ctx, "teambinding.List") + defer span.End() + ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err diff --git a/pkg/registry/apis/iam/user/legacy_search.go b/pkg/registry/apis/iam/user/legacy_search.go index 534a4f016eb..7fb6c13f7a6 100644 --- a/pkg/registry/apis/iam/user/legacy_search.go +++ b/pkg/registry/apis/iam/user/legacy_search.go @@ -6,6 +6,7 @@ import ( "log/slog" "math" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -27,13 +28,15 @@ type UserLegacySearchClient struct { resourcepb.ResourceIndexClient userService user.Service log *slog.Logger + tracer trace.Tracer } // NewUserLegacySearchClient creates a new UserLegacySearchClient. -func NewUserLegacySearchClient(userService user.Service) *UserLegacySearchClient { +func NewUserLegacySearchClient(userService user.Service, tracer trace.Tracer) *UserLegacySearchClient { return &UserLegacySearchClient{ userService: userService, log: slog.Default().With("logger", "legacy-user-search-client"), + tracer: tracer, } } @@ -41,6 +44,9 @@ func NewUserLegacySearchClient(userService user.Service) *UserLegacySearchClient // 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() + signedInUser, err := identity.GetRequester(ctx) if err != nil { return nil, err diff --git a/pkg/registry/apis/iam/user/legacy_search_test.go b/pkg/registry/apis/iam/user/legacy_search_test.go index b8a754f34ce..6bd49185786 100644 --- a/pkg/registry/apis/iam/user/legacy_search_test.go +++ b/pkg/registry/apis/iam/user/legacy_search_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/usertest" res "github.com/grafana/grafana/pkg/storage/unified/resource" @@ -17,7 +18,7 @@ import ( 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) + client := NewUserLegacySearchClient(mockUserService, tracing.NewNoopTracerService()) ctx := identity.WithRequester(context.Background(), &user.SignedInUser{OrgID: 1, UserID: 1}) req := &resourcepb.ResourceSearchRequest{ Options: &resourcepb.ListOptions{ @@ -66,7 +67,7 @@ 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) + client := NewUserLegacySearchClient(mockUserService, tracing.NewNoopTracerService()) ctx := identity.WithRequester(context.Background(), &user.SignedInUser{OrgID: 1, UserID: 1}) req := &resourcepb.ResourceSearchRequest{ Limit: 10, @@ -125,7 +126,7 @@ 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) + client := NewUserLegacySearchClient(mockUserService, tracing.NewNoopTracerService()) ctx := identity.WithRequester(context.Background(), &user.SignedInUser{OrgID: 1, UserID: 1}) req := &resourcepb.ResourceSearchRequest{ Options: &resourcepb.ListOptions{ diff --git a/pkg/registry/apis/iam/user/store.go b/pkg/registry/apis/iam/user/store.go index 803b2972b85..97daee3af18 100644 --- a/pkg/registry/apis/iam/user/store.go +++ b/pkg/registry/apis/iam/user/store.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "go.opentelemetry.io/otel/trace" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -36,18 +37,22 @@ var ( var resource = iamv0alpha1.UserResourceInfo -func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient, enableAuthnMutation bool) *LegacyStore { - return &LegacyStore{store, ac, enableAuthnMutation} +func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient, enableAuthnMutation bool, tracer trace.Tracer) *LegacyStore { + return &LegacyStore{store, ac, enableAuthnMutation, tracer} } type LegacyStore struct { store legacy.LegacyIdentityStore ac claims.AccessClient enableAuthnMutation bool + tracer trace.Tracer } // Update implements rest.Updater. func (s *LegacyStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + ctx, span := s.tracer.Start(ctx, "user.Update") + defer span.End() + if !s.enableAuthnMutation { return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "update") } @@ -105,6 +110,9 @@ func (s *LegacyStore) DeleteCollection(ctx context.Context, deleteValidation res // Delete implements rest.GracefulDeleter. func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + ctx, span := s.tracer.Start(ctx, "user.Delete") + defer span.End() + if !s.enableAuthnMutation { return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "delete") } @@ -171,6 +179,9 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, } func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "user.List") + defer span.End() + res, err := common.List( ctx, resource, s.ac, common.PaginationFromListOptions(options), func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0alpha1.User], error) { @@ -206,6 +217,9 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt } func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "user.Get") + defer span.End() + ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -229,6 +243,9 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO // Create implements rest.Creater. func (s *LegacyStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + ctx, span := s.tracer.Start(ctx, "user.Create") + defer span.End() + if !s.enableAuthnMutation { return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "create") } From 18280e1aa666e604d653eea57bd047f126a34608 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:48:19 -0500 Subject: [PATCH 4/6] Chore: Add owner for quotas in go.mod (#115039) --- go.mod | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 89590cb3a07..40e3288158d 100644 --- a/go.mod +++ b/go.mod @@ -250,6 +250,7 @@ require ( github.com/grafana/grafana/apps/plugins v0.0.0 // @grafana/plugins-platform-backend github.com/grafana/grafana/apps/preferences v0.0.0 // @grafana/grafana-app-platform-squad github.com/grafana/grafana/apps/provisioning v0.0.0 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2 // @grafana/grafana-search-and-storage github.com/grafana/grafana/apps/scope v0.0.0 // @grafana/grafana-operator-experience-squad github.com/grafana/grafana/apps/secret v0.0.0 // @grafana/grafana-operator-experience-squad github.com/grafana/grafana/apps/shorturl v0.0.0 // @grafana/sharing-squad @@ -659,8 +660,6 @@ require ( require github.com/grafana/tempo v1.5.1-0.20250529124718-87c2dc380cec // @grafana/observability-traces-and-profiling -require github.com/grafana/grafana/apps/quotas v0.0.0-20251209183543-1013d74f13f2 - require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/IBM/pgxpoolprometheus v1.1.2 // indirect From 83b0b14af692af77578210c27c397608edd5d380 Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Tue, 9 Dec 2025 14:53:55 -0500 Subject: [PATCH 5/6] Suggestions: Hook project up to auto-triaging (#114984) --- .github/commands.json | 8 ++++++++ .github/pr-commands.json | 10 ++++++++++ .github/workflows/auto-triager/labels.txt | 1 + 3 files changed, 19 insertions(+) diff --git a/.github/commands.json b/.github/commands.json index 29106576726..0db8a607b53 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -1226,5 +1226,13 @@ "addToProject": { "url": "https://github.com/orgs/grafana/projects/69" } + }, + { + "type": "label", + "name": "area/suggestions", + "action": "addToProject", + "addToProject": { + "url": "https://github.com/orgs/grafana/projects/56" + } } ] diff --git a/.github/pr-commands.json b/.github/pr-commands.json index 8b4472fe02a..ab42e204fce 100644 --- a/.github/pr-commands.json +++ b/.github/pr-commands.json @@ -469,5 +469,15 @@ "addToProject": { "url": "https://github.com/orgs/grafana/projects/190" } + }, + { + "type": "changedfiles", + "matches": [ + "public/app/features/panel/suggestions/**/*", + "public/app/plugins/panel/**/suggestions.ts", + "packages/grafana-data/src/types/suggestions*" + ], + "action": "updateLabel", + "addLabel": "area/suggestions" } ] diff --git a/.github/workflows/auto-triager/labels.txt b/.github/workflows/auto-triager/labels.txt index 18ef2df93d8..451719ee0f7 100644 --- a/.github/workflows/auto-triager/labels.txt +++ b/.github/workflows/auto-triager/labels.txt @@ -85,6 +85,7 @@ area/scenes area/search area/security area/streaming +area/suggestions area/templating/repeating area/tooltip area/transformations From 6bbb00d0a28dbbf153b320842c60ec058901e385 Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Tue, 9 Dec 2025 12:23:27 -0800 Subject: [PATCH 6/6] Short URL: Change default expiration to never (#115029) * Short Links: Change default expiration to never expire (-1) Previously, short links defaulted to expiring after 7 days. This change updates the default to -1 (never expire) to prevent automatic deletion of shared dashboard links. Changes: - conf/defaults.ini: Set expire_time = -1 and update comment - conf/sample.ini: Set expire_time = -1 and update comment - pkg/setting/setting.go: Update MustInt default from 7 to -1 The cleanup logic already handles -1 correctly (only runs when > 0), so no changes needed there. This unblocks progress on short URL feature improvements by ensuring shared links remain accessible indefinitely by default. * fix go * update docs / comments * update missed comment in sample.ini * Revert "fix go" This reverts commit e0d099ae31dcd27bacc0086103f01da33bc76723. * chore: update workspace dependencies Run 'make update-workspace' to sync Go workspace dependencies. This updates go.mod and go.sum files to match the current workspace state. * chore: add modowner for apps/quotas dependency Assign @grafana/grafana-search-and-storage as owner for apps/quotas dependency to satisfy modowners CI check. --- conf/defaults.ini | 5 ++--- conf/sample.ini | 4 ++-- .../setup-grafana/configure-grafana/_index.md | 12 ++++-------- pkg/setting/setting.go | 2 +- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index a72b0183290..c2d7e4da3b6 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1747,9 +1747,8 @@ enabled = true #################################### Short Links ############################# [short_links] -# Short links that are never accessed will be deleted as cleanup. Time is set up in days. The default is 7 days. Maximum value is 365. -# 0 means they will be deleted approximately every 10 minutes. A negative value (such as -1) will disable expiration. -expire_time = 7 +# Short links that are never accessed will be deleted as cleanup. Time is set up in days. The default is -1 (never expire). Maximum value is 365. +expire_time = -1 #################################### Internal Grafana Metrics ############ # Metrics available at HTTP URL /metrics and /metrics/plugins/:pluginId diff --git a/conf/sample.ini b/conf/sample.ini index d6397f894e4..d1d50f0a72a 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1689,8 +1689,8 @@ default_datasource_uid = #################################### Short Links ############################# [short_links] -# Short links which are never accessed will be deleted as cleanup. Time is in days. Default is 7 days. Max is 365. 0 means they will be deleted approximately every 10 minutes. -;expire_time = 7 +# Short links that are never accessed will be deleted as cleanup. Time is set up in days. The default is -1 (never expire). Maximum value is 365. +;expire_time = -1 #################################### Internal Grafana Metrics ########################## # Metrics available at HTTP URL /metrics and /metrics/plugins/:pluginId diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 13c2b77fd2f..1a3e4aea652 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -2142,17 +2142,13 @@ Configures settings around the short link feature. #### `expire_time` -Short links that are never accessed are considered expired or stale and are deleted as cleanup. +Short links that are never accessed are considered expired or stale and can be deleted as cleanup. Set the expiration time in days. -The default is `7` days. +The default is `-1` days (never expire). The maximum is `365` days. -A setting above the maximum uses the value `365` instead. -Setting `0` means the short links are cleaned up approximately every 10 minutes. -A negative value such as `-1` disables expiry. -{{< admonition type="caution" >}} -Short links without an expiration increase the size of the database and can't be deleted. Grafana recommends setting a duration based on your specific use case -{{< /admonition >}} +A setting above the maximum uses the value `365` instead. +A negative value such as `-1` disables expiry.
diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index f6c0b3d3f19..cc59427da6f 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -1330,7 +1330,7 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { cfg.QueryHistoryEnabled = queryHistory.Key("enabled").MustBool(true) shortLinks := iniFile.Section("short_links") - cfg.ShortLinkExpiration = shortLinks.Key("expire_time").MustInt(7) + cfg.ShortLinkExpiration = shortLinks.Key("expire_time").MustInt(-1) if cfg.ShortLinkExpiration > 365 { cfg.Logger.Warn("short_links expire_time must be less than 366 days. Setting to 365 days")