Merge remote-tracking branch 'origin' into attempt_at_short_url_updates

This commit is contained in:
nmarrs
2025-12-09 12:36:55 -08:00
22 changed files with 188 additions and 38 deletions
+8
View File
@@ -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"
}
}
]
+10
View File
@@ -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"
}
]
@@ -85,6 +85,7 @@ area/scenes
area/search
area/security
area/streaming
area/suggestions
area/templating/repeating
area/tooltip
area/transformations
+2 -2
View File
@@ -853,8 +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-20251209171149-4b999cd94388 h1:ZGYKVzeRlBWSf1e17Q8ME2WT1WmEKP8g0tMFfaBjgfI=
github.com/grafana/grafana/apps/quotas v0.0.0-20251209171149-4b999cd94388/go.mod h1:M7bV60iRB61y0ISPG1HX/oNLZtlh0ZF22rUYwNkAKjo=
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=
+2 -3
View File
@@ -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
+2 -2
View File
@@ -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
@@ -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.
<hr>
+1
View File
@@ -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
+2 -2
View File
@@ -1653,8 +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-20251209171149-4b999cd94388 h1:ZGYKVzeRlBWSf1e17Q8ME2WT1WmEKP8g0tMFfaBjgfI=
github.com/grafana/grafana/apps/quotas v0.0.0-20251209171149-4b999cd94388/go.mod h1:M7bV60iRB61y0ISPG1HX/oNLZtlh0ZF22rUYwNkAKjo=
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=
+6 -6
View File
@@ -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)
+16 -2
View File
@@ -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
+16 -2
View File
@@ -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
+19 -2
View File
@@ -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")
}
+19 -2
View File
@@ -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
+7 -1
View File
@@ -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
@@ -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{
+19 -2
View File
@@ -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")
}
+1 -1
View File
@@ -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")
+7
View File
@@ -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)
}
@@ -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 }));
});
@@ -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);
@@ -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.