Chore: Add support bundle for folders (#83360)

* add support bundle for folders

* fix ProvideService in tests

* add a test for collector
This commit is contained in:
Serge Zaitsev 2024-02-26 11:27:22 +01:00 committed by GitHub
parent ae00b4fb53
commit d0679f0993
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 163 additions and 18 deletions

View File

@ -56,6 +56,7 @@ import (
publicdashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/star/startest"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/usertest"
@ -829,7 +830,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
dashboardPermissions := accesscontrolmock.NewMockedPermissionsService()
folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()),
cfg, dashboardStore, folderStore, db.InitTestDB(t), features, nil)
cfg, dashboardStore, folderStore, db.InitTestDB(t), features, supportbundlestest.NewFakeBundleService(), nil)
if dashboardService == nil {
dashboardService, err = service.ProvideDashboardServiceImpl(

View File

@ -41,6 +41,7 @@ import (
"github.com/grafana/grafana/pkg/services/star"
"github.com/grafana/grafana/pkg/services/star/startest"
"github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/team/teamimpl"
@ -453,7 +454,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
folderStore := folderimpl.ProvideDashboardFolderStore(sc.db)
ac := acimpl.ProvideAccessControl(sc.cfg)
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sc.cfg, dashStore, folderStore, sc.db, features, nil)
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sc.cfg, dashStore, folderStore, sc.db, features, supportbundlestest.NewFakeBundleService(), nil)
cfg := setting.NewCfg()
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(

View File

@ -26,6 +26,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
@ -226,7 +227,7 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) {
})
ac := acimpl.ProvideAccessControl(sql.Cfg)
folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sql.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(sql), sql, features, nil)
folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sql.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(sql), sql, features, supportbundlestest.NewFakeBundleService(), nil)
cfg := setting.NewCfg()
cfg.AnnotationMaximumTagsLength = 60

View File

@ -295,7 +295,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) {
guardian.New = origNewGuardian
})
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore), sqlStore, features, nil)
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore), sqlStore, features, supportbundlestest.NewFakeBundleService(), nil)
parentUID := ""
for i := 0; ; i++ {

View File

@ -26,6 +26,7 @@ import (
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
@ -712,7 +713,7 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) {
ac := acimpl.ProvideAccessControl(sqlStore.Cfg)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, nil)
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil)
user := &user.SignedInUser{
OrgID: 1,
@ -829,7 +830,7 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) {
ac := acimpl.ProvideAccessControl(sqlStore.Cfg)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, nil)
folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil)
user := &user.SignedInUser{
OrgID: 1,

View File

@ -2,6 +2,7 @@ package folderimpl
import (
"context"
"encoding/json"
"errors"
"fmt"
"runtime"
@ -27,6 +28,8 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/supportbundles"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@ -40,7 +43,6 @@ 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 bus.Bus
@ -57,6 +59,7 @@ func ProvideService(
folderStore folder.FolderStore,
db db.DB, // DB for the (new) nested folder store
features featuremgmt.FeatureToggles,
supportBundles supportbundles.Service,
r prometheus.Registerer,
) folder.Service {
store := ProvideStore(db, cfg)
@ -75,6 +78,8 @@ func ProvideService(
}
srv.DBMigration(db)
supportBundles.RegisterSupportItemCollector(srv.supportBundleCollector())
ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(folderStore, srv))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
@ -1181,3 +1186,66 @@ func (s *Service) RegisterService(r folder.RegistryService) error {
return nil
}
func (s *Service) supportBundleCollector() supportbundles.Collector {
collector := supportbundles.Collector{
UID: "folder-stats",
DisplayName: "Folder information",
Description: "Folder information for the Grafana instance",
IncludedByDefault: false,
Default: true,
Fn: func(ctx context.Context) (*supportbundles.SupportItem, error) {
s.log.Info("Generating folder support bundle")
folders, err := s.GetFolders(ctx, folder.GetFoldersQuery{
OrgID: 0,
SignedInUser: &user.SignedInUser{
Login: "sa-supportbundle",
OrgRole: "Admin",
IsGrafanaAdmin: true,
IsServiceAccount: true,
Permissions: map[int64]map[string][]string{accesscontrol.GlobalOrgID: {dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll}}},
},
})
if err != nil {
return nil, err
}
return s.supportItemFromFolders(folders)
},
}
return collector
}
func (s *Service) supportItemFromFolders(folders []*folder.Folder) (*supportbundles.SupportItem, error) {
stats := struct {
Total int `json:"total"` // how many folders?
Depths map[int]int `json:"depths"` // how deep they are?
Children map[int]int `json:"children"` // how many child folders they have?
Folders []*folder.Folder `json:"folders"` // what are they?
}{Total: len(folders), Folders: folders, Children: map[int]int{}, Depths: map[int]int{}}
// Build parent-child mapping
parents := map[string]string{}
children := map[string][]string{}
for _, f := range folders {
parents[f.UID] = f.ParentUID
children[f.ParentUID] = append(children[f.ParentUID], f.UID)
}
// Find depths of each folder
for _, f := range folders {
depth := 0
for uid := f.UID; uid != ""; uid = parents[uid] {
depth++
}
stats.Depths[depth] += 1
stats.Children[len(children[f.UID])] += 1
}
b, err := json.MarshalIndent(stats, "", " ")
if err != nil {
return nil, err
}
return &supportbundles.SupportItem{
Filename: "folders.json",
FileBytes: b,
}, nil
}

View File

@ -2,6 +2,7 @@ package folderimpl
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/rand"
@ -40,6 +41,7 @@ import (
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
@ -57,7 +59,7 @@ func TestIntegrationProvideFolderService(t *testing.T) {
cfg := setting.NewCfg()
ac := acmock.New()
db := sqlstore.InitTestDB(t)
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, db, &featuremgmt.FeatureManager{}, nil)
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, db, &featuremgmt.FeatureManager{}, supportbundlestest.NewFakeBundleService(), nil)
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 3)
})
@ -1798,6 +1800,70 @@ func TestFolderServiceGetFolders(t *testing.T) {
})
}
func TestSupportBundle(t *testing.T) {
f := func(uid, parent string) *folder.Folder { return &folder.Folder{UID: uid, ParentUID: parent} }
for _, tc := range []struct {
Folders []*folder.Folder
ExpectedTotal int
ExpectedDepths map[int]int
ExpectedChildren map[int]int
}{
// Empty folder list
{
Folders: []*folder.Folder{},
ExpectedTotal: 0,
ExpectedDepths: map[int]int{},
ExpectedChildren: map[int]int{},
},
// Single folder
{
Folders: []*folder.Folder{f("a", "")},
ExpectedTotal: 1,
ExpectedDepths: map[int]int{1: 1},
ExpectedChildren: map[int]int{0: 1},
},
// Flat folders
{
Folders: []*folder.Folder{f("a", ""), f("b", ""), f("c", "")},
ExpectedTotal: 3,
ExpectedDepths: map[int]int{1: 3},
ExpectedChildren: map[int]int{0: 3},
},
// Nested folders
{
Folders: []*folder.Folder{f("a", ""), f("ab", "a"), f("ac", "a"), f("x", ""), f("xy", "x"), f("xyz", "xy")},
ExpectedTotal: 6,
ExpectedDepths: map[int]int{1: 2, 2: 3, 3: 1},
ExpectedChildren: map[int]int{0: 3, 1: 2, 2: 1},
},
} {
svc := &Service{}
supportItem, err := svc.supportItemFromFolders(tc.Folders)
if err != nil {
t.Fatal(err)
}
stats := struct {
Total int `json:"total"`
Depths map[int]int `json:"depths"`
Children map[int]int `json:"children"`
}{}
if err := json.Unmarshal(supportItem.FileBytes, &stats); err != nil {
t.Fatal(err)
}
if stats.Total != tc.ExpectedTotal {
t.Error("Total mismatch", stats, tc)
}
if fmt.Sprint(stats.Depths) != fmt.Sprint(tc.ExpectedDepths) {
t.Error("Depths mismatch", stats, tc.ExpectedDepths)
}
if fmt.Sprint(stats.Children) != fmt.Sprint(tc.ExpectedChildren) {
t.Error("Depths mismatch", stats, tc.ExpectedChildren)
}
}
}
func CreateSubtreeInStore(t *testing.T, store store, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []*folder.Folder {
t.Helper()

View File

@ -464,8 +464,11 @@ func (ss *sqlStore) GetFolders(ctx context.Context, q getFoldersQuery) ([]*folde
s.WriteString(getFullpathJoinsSQL())
}
// covered by UQE_folder_org_id_uid
s.WriteString(` WHERE f0.org_id=?`)
args := []any{q.OrgID}
args := []any{}
if q.OrgID > 0 {
s.WriteString(` WHERE f0.org_id=?`)
args = []any{q.OrgID}
}
if len(partialUIDs) > 0 {
s.WriteString(` AND f0.uid IN (?` + strings.Repeat(", ?", len(partialUIDs)-1) + `)`)
for _, uid := range partialUIDs {

View File

@ -327,7 +327,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sc.sqlStore, features, nil)
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil)
t.Logf("Creating folder with title and UID %q", title)
ctx := appcontext.WithUser(context.Background(), &sc.user)
folder, err := s.Create(ctx, &folder.CreateFolderCommand{
@ -460,7 +460,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
Cfg: sqlStore.Cfg,
features: featuremgmt.WithFeatures(),
SQLStore: sqlStore,
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, nil),
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil),
}
// deliberate difference between signed in user and user in db to make it crystal clear

View File

@ -759,7 +759,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder
dashboardStore, err := database.ProvideDashboardStore(sc.sqlStore, cfg, features, tagimpl.ProvideService(sc.sqlStore), quotaService)
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore)
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sc.sqlStore, features, nil)
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil)
t.Logf("Creating folder with title and UID %q", title)
ctx := appcontext.WithUser(context.Background(), sc.user)
@ -841,7 +841,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService)
require.NoError(t, err)
features := featuremgmt.WithFeatures()
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features, nil)
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil)
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, featuremgmt.WithFeatures(), ac)
service := LibraryPanelService{

View File

@ -30,6 +30,7 @@ import (
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/team/teamimpl"
"github.com/grafana/grafana/pkg/services/user/userimpl"
@ -69,7 +70,7 @@ func NewTestMigrationStore(t testing.TB, sqlStore *sqlstore.SQLStore, cfg *setti
dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, features, tagimpl.ProvideService(sqlStore), quotaService)
require.NoError(t, err)
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, sqlStore, features, nil)
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil)
err = folderService.RegisterService(alertingStore)
require.NoError(t, err)

View File

@ -20,13 +20,14 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/setting"
)
func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStore dashboards.Store, folderStore *folderimpl.DashboardFolderStoreImpl, bus *bus.InProcBus, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service {
tb.Helper()
return folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, db, features, nil)
return folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, db, features, supportbundlestest.NewFakeBundleService(), nil)
}
func SetupDashboardService(tb testing.TB, sqlStore *sqlstore.SQLStore, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {

View File

@ -29,6 +29,7 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/tests/testsuite"
@ -702,7 +703,7 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, features, tagimpl.ProvideService(db), quotatest.New(false, nil))
require.NoError(t, err)
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), db.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(db), db, features, nil)
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), db.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(db), db, features, supportbundlestest.NewFakeBundleService(), nil)
// create parent folder
parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{

View File

@ -27,6 +27,7 @@ import (
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
)
@ -83,7 +84,7 @@ func setupBenchMark(b *testing.B, usr user.SignedInUser, features featuremgmt.Fe
dashboardWriteStore, err := database.ProvideDashboardStore(store, store.Cfg, features, tagimpl.ProvideService(store), quotaService)
require.NoError(b, err)
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), store.Cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store), store, features, nil)
folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), store.Cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store), store, features, supportbundlestest.NewFakeBundleService(), nil)
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true})