package federated import ( "context" "encoding/json" "testing" "time" "github.com/stretchr/testify/require" "k8s.io/apiserver/pkg/endpoints/request" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngalertstore "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/storage/legacysql" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/util/testutil" ) func TestMain(m *testing.M) { testsuite.Run(m) } func TestIntegrationDirectSQLStats(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) db, cfg := db.InitTestDBWithCfg(t) ctx := context.Background() dashStore, err := database.ProvideDashboardStore(db, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db)) require.NoError(t, err) fStore := folderimpl.ProvideStore(db) tempUser := &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{}} folder1UID := "test1" now := time.Now() dashFolder1 := dashboards.NewDashboardFolder("test1") dashFolder1.SetUID(folder1UID) dashFolder1.OrgID = 1 dashFolder1.CreatedBy = tempUser.UserID dashFolder1.UpdatedBy = tempUser.UserID _, err = dashStore.SaveDashboard(ctx, dashboards.SaveDashboardCommand{ Dashboard: dashFolder1.Data, OrgID: 1, UserID: tempUser.UserID, IsFolder: true, }) require.NoError(t, err) _, err = fStore.Create(ctx, folder.CreateFolderCommand{Title: "test1", UID: folder1UID, OrgID: 1, SignedInUser: tempUser}) require.NoError(t, err) folder2UID := "test2" dashFolder2 := dashboards.NewDashboardFolder("test2") dashFolder2.SetUID(folder2UID) dashFolder2.OrgID = 1 dashFolder2.FolderUID = folder1UID dashFolder2.CreatedBy = tempUser.UserID dashFolder2.UpdatedBy = tempUser.UserID _, err = dashStore.SaveDashboard(ctx, dashboards.SaveDashboardCommand{ Dashboard: dashFolder2.Data, OrgID: 1, UserID: tempUser.UserID, IsFolder: true, FolderUID: folder1UID, }) require.NoError(t, err) _, err = fStore.Create(ctx, folder.CreateFolderCommand{Title: "test2", UID: folder2UID, OrgID: 1, ParentUID: folder1UID, SignedInUser: tempUser}) require.NoError(t, err) ruleStore := ngalertstore.SetupStoreForTesting(t, db) dashboardUID := "test" _, err = ruleStore.InsertAlertRules(context.Background(), ngmodels.NewUserUID(tempUser), []ngmodels.InsertRule{{ AlertRule: ngmodels.AlertRule{ DashboardUID: &dashboardUID, UID: "test", Title: "test", OrgID: 1, Data: []ngmodels.AlertQuery{ { RefID: "A", Model: json.RawMessage("{}"), DatasourceUID: expr.DatasourceUID, RelativeTimeRange: ngmodels.RelativeTimeRange{ From: ngmodels.Duration(60), To: ngmodels.Duration(0), }, }, }, Condition: "ok", Updated: now, NamespaceUID: folder2UID, ExecErrState: ngmodels.ExecutionErrorState(ngmodels.Alerting), NoDataState: ngmodels.Alerting, IntervalSeconds: 60, }}}) require.NoError(t, err) _, err = dashStore.SaveDashboard(ctx, dashboards.SaveDashboardCommand{ Dashboard: simplejson.New(), FolderUID: folder1UID, OrgID: 1, }) require.NoError(t, err) store := &LegacyStatsGetter{ SQL: legacysql.NewDatabaseProvider(db), } t.Run("GetStatsForFolder1", func(t *testing.T) { ctx := context.Background() ctx = request.WithNamespace(ctx, "default") stats, err := store.GetStats(ctx, &resourcepb.ResourceStatsRequest{ Namespace: "default", Folder: folder1UID, }) require.NoError(t, err) jj, _ := json.MarshalIndent(stats.Stats, "", " ") require.JSONEq(t, `[ { "group": "sql-fallback", "resource": "alertrules" }, { "group": "sql-fallback", "resource": "dashboards", "count": 1 }, { "group": "sql-fallback", "resource": "folders", "count": 1 }, { "group": "sql-fallback", "resource": "library_elements" } ]`, string(jj)) }) // New tests to verify per-resource fallback disabling t.Run("GetStatsForFolder1_DisableDashboardsFallback", func(t *testing.T) { ctx := context.Background() ctx = request.WithNamespace(ctx, "default") store := &LegacyStatsGetter{ SQL: legacysql.NewDatabaseProvider(db), DisableSQLFallbackDashboards: true, DisableSQLFallbackFolders: false, } stats, err := store.GetStats(ctx, &resourcepb.ResourceStatsRequest{ Namespace: "default", Folder: folder1UID, }) require.NoError(t, err) var hasDashboards, hasFolders bool for _, s := range stats.Stats { if s.Resource == "dashboards" { hasDashboards = true } if s.Resource == "folders" { hasFolders = true require.EqualValues(t, 1, s.Count) } } require.False(t, hasDashboards, "dashboards stats should be disabled") require.True(t, hasFolders, "folders stats should be present") }) t.Run("GetStatsForFolder1_DisableFoldersFallback", func(t *testing.T) { ctx := context.Background() ctx = request.WithNamespace(ctx, "default") store := &LegacyStatsGetter{ SQL: legacysql.NewDatabaseProvider(db), DisableSQLFallbackDashboards: false, DisableSQLFallbackFolders: true, } stats, err := store.GetStats(ctx, &resourcepb.ResourceStatsRequest{ Namespace: "default", Folder: folder1UID, }) require.NoError(t, err) var hasDashboards, hasFolders bool for _, s := range stats.Stats { if s.Resource == "dashboards" { hasDashboards = true require.EqualValues(t, 1, s.Count) } if s.Resource == "folders" { hasFolders = true } } require.True(t, hasDashboards, "dashboards stats should be present") require.False(t, hasFolders, "folders stats should be disabled") }) t.Run("GetStatsForFolder1_DisableBothFallbacks", func(t *testing.T) { ctx := context.Background() ctx = request.WithNamespace(ctx, "default") store := &LegacyStatsGetter{ SQL: legacysql.NewDatabaseProvider(db), DisableSQLFallbackDashboards: true, DisableSQLFallbackFolders: true, } stats, err := store.GetStats(ctx, &resourcepb.ResourceStatsRequest{ Namespace: "default", Folder: folder1UID, }) require.NoError(t, err) var hasDashboards, hasFolders bool var hasAlertRules, hasLibrary bool for _, s := range stats.Stats { if s.Resource == "dashboards" { hasDashboards = true } if s.Resource == "folders" { hasFolders = true } if s.Resource == "alertrules" { hasAlertRules = true } if s.Resource == "library_elements" { hasLibrary = true } } require.False(t, hasDashboards, "dashboards stats should be disabled") require.False(t, hasFolders, "folders stats should be disabled") require.True(t, hasAlertRules, "alertrules should still be present") require.True(t, hasLibrary, "library_elements should still be present") }) t.Run("GetStatsForFolder2", func(t *testing.T) { ctx := context.Background() ctx = request.WithNamespace(ctx, "default") stats, err := store.GetStats(ctx, &resourcepb.ResourceStatsRequest{ Namespace: "default", Folder: folder2UID, }) require.NoError(t, err) jj, _ := json.MarshalIndent(stats.Stats, "", " ") require.JSONEq(t, `[ { "group": "sql-fallback", "resource": "alertrules", "count": 1 }, { "group": "sql-fallback", "resource": "dashboards" }, { "group": "sql-fallback", "resource": "folders" }, { "group": "sql-fallback", "resource": "library_elements" } ]`, string(jj)) }) }