Alerting: update rule versions on folder move (#88376)
* Alerting: update rule versions on folder move (#88361) * Add tracing to folder.Move and folder.Update
This commit is contained in:
@@ -13,6 +13,8 @@ import (
|
||||
|
||||
"github.com/grafana/dskit/concurrency"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
@@ -20,6 +22,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/events"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||
@@ -44,12 +47,14 @@ type Service struct {
|
||||
dashboardFolderStore folder.FolderStore
|
||||
features featuremgmt.FeatureToggles
|
||||
accessControl accesscontrol.AccessControl
|
||||
// bus is currently used to publish event in case of title change
|
||||
// bus is currently used to publish event in case of folder full path change.
|
||||
// For example when a folder is moved to another folder or when a folder is renamed.
|
||||
bus bus.Bus
|
||||
|
||||
mutex sync.RWMutex
|
||||
registry map[string]folder.RegistryService
|
||||
metrics *foldersMetrics
|
||||
tracer tracing.Tracer
|
||||
}
|
||||
|
||||
func ProvideService(
|
||||
@@ -61,6 +66,7 @@ func ProvideService(
|
||||
features featuremgmt.FeatureToggles,
|
||||
supportBundles supportbundles.Service,
|
||||
r prometheus.Registerer,
|
||||
tracer tracing.Tracer,
|
||||
) folder.Service {
|
||||
store := ProvideStore(db)
|
||||
srv := &Service{
|
||||
@@ -74,6 +80,7 @@ func ProvideService(
|
||||
db: db,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
metrics: newFoldersMetrics(r),
|
||||
tracer: tracer,
|
||||
}
|
||||
srv.DBMigration(db)
|
||||
|
||||
@@ -655,6 +662,9 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
|
||||
}
|
||||
|
||||
func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.Update")
|
||||
defer span.End()
|
||||
|
||||
if cmd.SignedInUser == nil {
|
||||
return nil, folder.ErrBadRequest.Errorf("missing signed in user")
|
||||
}
|
||||
@@ -679,14 +689,8 @@ func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (
|
||||
|
||||
if cmd.NewTitle != nil {
|
||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc()
|
||||
if err := s.bus.Publish(ctx, &events.FolderTitleUpdated{
|
||||
Timestamp: foldr.Updated,
|
||||
Title: foldr.Title,
|
||||
ID: dashFolder.ID, // nolint:staticcheck
|
||||
UID: dashFolder.UID,
|
||||
OrgID: cmd.OrgID,
|
||||
}); err != nil {
|
||||
s.log.ErrorContext(ctx, "failed to publish FolderTitleUpdated event", "folder", foldr.Title, "user", cmd.SignedInUser.GetID(), "error", err)
|
||||
|
||||
if err := s.publishFolderFullPathUpdatedEvent(ctx, foldr.Updated, cmd.OrgID, cmd.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -873,6 +877,9 @@ func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderComm
|
||||
}
|
||||
|
||||
func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*folder.Folder, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.Move")
|
||||
defer span.End()
|
||||
|
||||
if cmd.SignedInUser == nil {
|
||||
return nil, folder.ErrBadRequest.Errorf("missing signed in user")
|
||||
}
|
||||
@@ -947,6 +954,10 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
|
||||
return folder.ErrInternal.Errorf("failed to move legacy folder: %w", err)
|
||||
}
|
||||
|
||||
if err := s.publishFolderFullPathUpdatedEvent(ctx, f.Updated, cmd.OrgID, cmd.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
@@ -954,6 +965,36 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (s *Service) publishFolderFullPathUpdatedEvent(ctx context.Context, timestamp time.Time, orgID int64, folderUID string) error {
|
||||
ctx, span := s.tracer.Start(ctx, "folder.publishFolderFullPathUpdatedEvent")
|
||||
defer span.End()
|
||||
|
||||
descFolders, err := s.store.GetDescendants(ctx, orgID, folderUID)
|
||||
if err != nil {
|
||||
s.log.ErrorContext(ctx, "Failed to get descendants of the folder", "folderUID", folderUID, "orgID", orgID, "error", err)
|
||||
return err
|
||||
}
|
||||
uids := make([]string, 0, len(descFolders)+1)
|
||||
uids = append(uids, folderUID)
|
||||
for _, f := range descFolders {
|
||||
uids = append(uids, f.UID)
|
||||
}
|
||||
span.AddEvent("found folder descendants", trace.WithAttributes(
|
||||
attribute.Int64("folders", int64(len(uids))),
|
||||
))
|
||||
|
||||
if err := s.bus.Publish(ctx, &events.FolderFullPathUpdated{
|
||||
Timestamp: timestamp,
|
||||
UIDs: uids,
|
||||
OrgID: orgID,
|
||||
}); err != nil {
|
||||
s.log.ErrorContext(ctx, "Failed to publish FolderFullPathUpdated event", "folderUID", folderUID, "orgID", orgID, "descendantsUIDs", uids, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) canMove(ctx context.Context, cmd *folder.MoveFolderCommand) (bool, error) {
|
||||
// Check that the user is allowed to move the folder to the destination folder
|
||||
var evaluator accesscontrol.Evaluator
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestIntegrationProvideFolderService(t *testing.T) {
|
||||
t.Run("should register scope resolvers", func(t *testing.T) {
|
||||
ac := acmock.New()
|
||||
db := db.InitTestDB(t)
|
||||
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), nil, nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil)
|
||||
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), nil, nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
|
||||
|
||||
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 3)
|
||||
})
|
||||
@@ -100,6 +100,7 @@ func TestIntegrationFolderService(t *testing.T) {
|
||||
accessControl: acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()),
|
||||
metrics: newFoldersMetrics(nil),
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
}
|
||||
|
||||
require.NoError(t, service.RegisterService(alertingStore))
|
||||
@@ -440,6 +441,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
accessControl: ac,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
}
|
||||
|
||||
signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
|
||||
@@ -553,6 +555,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
db: db,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
}
|
||||
|
||||
origNewGuardian := guardian.New
|
||||
@@ -630,6 +633,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
|
||||
db: db,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -805,6 +809,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
}
|
||||
t.Run("create folder", func(t *testing.T) {
|
||||
nestedFolderStore.ExpectedFolder = &folder.Folder{ParentUID: util.GenerateShortUID()}
|
||||
@@ -841,6 +846,7 @@ func TestFolderServiceDualWrite(t *testing.T) {
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders),
|
||||
accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
}
|
||||
|
||||
@@ -1475,6 +1481,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) {
|
||||
accessControl: ac,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
}
|
||||
|
||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||
@@ -1977,6 +1984,7 @@ func TestFolderServiceGetFolders(t *testing.T) {
|
||||
accessControl: ac,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
}
|
||||
|
||||
signedInAdminUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
|
||||
@@ -2063,6 +2071,7 @@ func TestGetChildrenFilterByPermission(t *testing.T) {
|
||||
accessControl: ac,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
}
|
||||
|
||||
origGuardian := guardian.New
|
||||
@@ -2523,6 +2532,7 @@ func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder
|
||||
accessControl: ac,
|
||||
db: db,
|
||||
metrics: newFoldersMetrics(nil),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user