From e019e34eb5cdce1234b96702dbdc57fd6d489e6f Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Thu, 16 Jan 2025 22:19:56 -0700 Subject: [PATCH] Stats: use dashboard stats rather than list (#99130) --- .../statscollector/concurrent_users_test.go | 5 +- pkg/registry/apis/dashboard/legacy/storage.go | 33 ++++++- pkg/services/dashboards/dashboard.go | 2 + .../dashboards/dashboard_service_mock.go | 30 +++++++ pkg/services/dashboards/database/database.go | 18 ++++ .../dashboards/database/database_test.go | 12 +++ .../dashboards/service/dashboard_service.go | 27 +++++- .../service/dashboard_service_test.go | 88 ++++++++++--------- pkg/services/dashboards/store_mock.go | 31 +++++++ pkg/services/stats/statsimpl/stats.go | 59 ++++--------- pkg/services/stats/statsimpl/stats_test.go | 21 ++--- 11 files changed, 226 insertions(+), 100 deletions(-) diff --git a/pkg/infra/usagestats/statscollector/concurrent_users_test.go b/pkg/infra/usagestats/statscollector/concurrent_users_test.go index 4d89fb51a4a8e..dbe348c4c8cad 100644 --- a/pkg/infra/usagestats/statscollector/concurrent_users_test.go +++ b/pkg/infra/usagestats/statscollector/concurrent_users_test.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/stats/statsimpl" @@ -28,7 +29,7 @@ func TestMain(m *testing.M) { func TestConcurrentUsersMetrics(t *testing.T) { sqlStore, cfg := db.InitTestDBWithCfg(t) - statsService := statsimpl.ProvideService(&setting.Cfg{}, sqlStore, &dashboards.FakeDashboardService{}, &foldertest.FakeService{}, &orgtest.FakeOrgService{}) + statsService := statsimpl.ProvideService(&setting.Cfg{}, sqlStore, &dashboards.FakeDashboardService{}, &foldertest.FakeService{}, &orgtest.FakeOrgService{}, featuremgmt.WithFeatures()) s := createService(t, cfg, sqlStore, statsService) createConcurrentTokens(t, sqlStore) @@ -46,7 +47,7 @@ func TestConcurrentUsersMetrics(t *testing.T) { func TestConcurrentUsersStats(t *testing.T) { sqlStore, cfg := db.InitTestDBWithCfg(t) - statsService := statsimpl.ProvideService(&setting.Cfg{}, sqlStore, &dashboards.FakeDashboardService{}, &foldertest.FakeService{}, &orgtest.FakeOrgService{}) + statsService := statsimpl.ProvideService(&setting.Cfg{}, sqlStore, &dashboards.FakeDashboardService{}, &foldertest.FakeService{}, &orgtest.FakeOrgService{}, featuremgmt.WithFeatures()) s := createService(t, cfg, sqlStore, statsService) createConcurrentTokens(t, sqlStore) diff --git a/pkg/registry/apis/dashboard/legacy/storage.go b/pkg/registry/apis/dashboard/legacy/storage.go index 10aece31f3114..f48a98a35fdd2 100644 --- a/pkg/registry/apis/dashboard/legacy/storage.go +++ b/pkg/registry/apis/dashboard/legacy/storage.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "time" "github.com/grafana/authlib/claims" @@ -340,5 +341,35 @@ func (a *dashboardSqlAccess) CountRepositoryObjects(context.Context, *resource.C // GetStats implements ResourceServer. func (a *dashboardSqlAccess) GetStats(ctx context.Context, req *resource.ResourceStatsRequest) (*resource.ResourceStatsResponse, error) { - return nil, fmt.Errorf("not yet (GetStats)") + info, err := claims.ParseNamespace(req.Namespace) + if err != nil { + return nil, fmt.Errorf("unable to read namespace") + } + if info.OrgID == 0 { + return nil, fmt.Errorf("invalid OrgID found in namespace") + } + + if len(req.Kinds) != 1 { + return nil, fmt.Errorf("only can query for dashboard kind in legacy fallback") + } + + parts := strings.SplitN(req.Kinds[0], "/", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid kind") + } + + count, err := a.dashStore.CountInOrg(ctx, info.OrgID) + if err != nil { + return nil, err + } + + return &resource.ResourceStatsResponse{ + Stats: []*resource.ResourceStatsResponse_Stats{ + { + Group: parts[0], + Resource: parts[1], + Count: count, + }, + }, + }, nil } diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index ff51f66622c55..ff46fbd5567bd 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -36,6 +36,7 @@ type DashboardService interface { RestoreDashboard(ctx context.Context, dashboard *Dashboard, user identity.Requester, optionalFolderUID string) error CleanUpDeletedDashboards(ctx context.Context) (int64, error) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*Dashboard, error) + CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) } // PluginService is a service for operating on plugin dashboards. @@ -82,6 +83,7 @@ type Store interface { ValidateDashboardBeforeSave(ctx context.Context, dashboard *Dashboard, overwrite bool) (bool, error) Count(context.Context, *quota.ScopeParameters) (*quota.Map, error) + CountInOrg(ctx context.Context, orgID int64) (int64, error) // CountDashboardsInFolder returns the number of dashboards associated with // the given parent folder ID. CountDashboardsInFolders(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error) diff --git a/pkg/services/dashboards/dashboard_service_mock.go b/pkg/services/dashboards/dashboard_service_mock.go index 329efe73e0ba9..af8a6184e2a76 100644 --- a/pkg/services/dashboards/dashboard_service_mock.go +++ b/pkg/services/dashboards/dashboard_service_mock.go @@ -168,6 +168,36 @@ func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *FindP return r0, r1 } +// CountDashboardsInOrg provides a mock function with given fields: ctx, orgID +func (_m *FakeDashboardService) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) { + ret := _m.Called(ctx, orgID) + + if len(ret) == 0 { + panic("no return value specified for CountDashboardsInOrg") + } + + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok { + return rf(ctx, orgID) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) int64); ok { + r0 = rf(ctx, orgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(int64) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, orgID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetAllDashboards provides a mock function with given fields: ctx func (_m *FakeDashboardService) GetAllDashboards(ctx context.Context) ([]*Dashboard, error) { ret := _m.Called(ctx) diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 5c19adb94279f..6a3fe4045045c 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -266,6 +266,24 @@ func (d *dashboardStore) Count(ctx context.Context, scopeParams *quota.ScopePara return u, nil } +func (d *dashboardStore) CountInOrg(ctx context.Context, orgID int64) (int64, error) { + type result struct { + Count int64 + } + r := result{} + if err := d.store.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM dashboard WHERE org_id=? AND is_folder=%s", d.store.GetDialect().BooleanStr(false)) + if _, err := sess.SQL(rawSQL, orgID).Get(&r); err != nil { + return err + } + return nil + }); err != nil { + return 0, err + } + + return r.Count, nil +} + func getExistingDashboardByIDOrUIDForUpdate(sess *db.Session, dash *dashboards.Dashboard, overwrite bool) (bool, error) { dashWithIdExists := false isParentFolderChanged := false diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index d54dd4066ccbe..34453b818c4d6 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -61,6 +61,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { savedDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, savedFolder.ID, savedFolder.UID, false, "prod", "webapp") insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.ID, savedFolder.UID, false, "prod") savedDash2 = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, "", false, "prod") + insertTestDashboard(t, dashboardStore, "test dash org2", 2, 0, "", false, "") insertTestRule(t, sqlStore, savedFolder.OrgID, savedFolder.UID) } @@ -81,6 +82,17 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { require.Positive(t, len(savedFolder.UID)) }) + t.Run("Should be able to get dashboard counts per org", func(t *testing.T) { + setup() + count, err := dashboardStore.CountInOrg(context.Background(), 1) + require.NoError(t, err) + require.Equal(t, int64(3), count) + + count, err = dashboardStore.CountInOrg(context.Background(), 2) + require.NoError(t, err) + require.Equal(t, int64(1), count) + }) + t.Run("Should be able to get dashboard by id", func(t *testing.T) { setup() query := dashboards.GetDashboardQuery{ diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 188200725e952..2bb3d84aa0cc4 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -180,11 +180,10 @@ func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.Sc total := int64(0) for _, org := range orgs { ctx = identity.WithRequester(ctx, getDashboardBackgroundRequester(org.ID)) - dashs, err := dr.listDashboardsThroughK8s(ctx, org.ID) + orgDashboards, err := dr.CountDashboardsInOrg(ctx, org.ID) if err != nil { - return u, err + return nil, err } - orgDashboards := int64(len(dashs)) total += orgDashboards tag, err := quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, quota.OrgScope) @@ -206,6 +205,28 @@ func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.Sc return dr.dashboardStore.Count(ctx, scopeParams) } +func (dr *DashboardServiceImpl) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + resp, err := dr.k8sclient.getSearcher().GetStats(ctx, &resource.ResourceStatsRequest{ + Namespace: dr.k8sclient.getNamespace(orgID), + Kinds: []string{ + v0alpha1.GROUP + "/" + v0alpha1.DashboardResourceInfo.GetName(), + }, + }) + if err != nil { + return 0, err + } + + if len(resp.Stats) != 1 { + return 0, fmt.Errorf("expected 1 stat, got %d", len(resp.Stats)) + } + + return resp.Stats[0].Count, nil + } + + return dr.dashboardStore.CountInOrg(ctx, orgID) +} + func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) { limits := "a.Map{} diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index a8768008f2b9d..2236b87caefa2 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -271,6 +271,11 @@ func (m *mockResourceIndexClient) Search(ctx context.Context, req *resource.Reso return args.Get(0).(*resource.ResourceSearchResponse), args.Error(1) } +func (m *mockResourceIndexClient) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) { + args := m.Called(in) + return args.Get(0).(*resource.ResourceStatsResponse), args.Error(1) +} + type mockResourceInterface struct { mock.Mock dynamic.ResourceInterface @@ -1779,44 +1784,17 @@ func TestQuotaCount(t *testing.T) { }, } - dashboardUnstructuredOrg1 := unstructured.UnstructuredList{ - Items: []unstructured.Unstructured{{ - Object: map[string]any{ - "metadata": map[string]any{ - "name": "uid", - }, - "spec": map[string]any{ - "test": "test", - "version": int64(1), - "title": "testing slugify", - }, - }, - }}, - } - dashboardUnstructuredOrg2 := unstructured.UnstructuredList{ - Items: []unstructured.Unstructured{{ - Object: map[string]any{ - "metadata": map[string]any{ - "name": "uid", - }, - "spec": map[string]any{ - "test": "test", - "version": int64(1), - "title": "testing slugify", - }, + countOrg1 := resource.ResourceStatsResponse{ + Stats: []*resource.ResourceStatsResponse_Stats{ + { + Count: 1, }, }, + } + countOrg2 := resource.ResourceStatsResponse{ + Stats: []*resource.ResourceStatsResponse_Stats{ { - Object: map[string]any{ - "metadata": map[string]any{ - "name": "uid2", - }, - "spec": map[string]any{ - "test": "test2", - "version": int64(1), - "title": "testing slugify2", - }, - }, + Count: 2, }, }, } @@ -1833,13 +1811,11 @@ func TestQuotaCount(t *testing.T) { }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { - ctx, k8sClientMock, k8sResourceMock := setupK8sDashboardTests(service) + ctx, k8sClientMock, _ := setupK8sDashboardTests(service) orgSvc := orgtest.FakeOrgService{ExpectedOrgs: orgs} service.orgService = &orgSvc - k8sClientMock.On("getClient", mock.Anything, int64(1)).Return(k8sResourceMock, true).Once() - k8sClientMock.On("getClient", mock.Anything, int64(2)).Return(k8sResourceMock, true).Once() - k8sResourceMock.On("List", mock.Anything, mock.Anything).Return(&dashboardUnstructuredOrg2, nil).Once() - k8sResourceMock.On("List", mock.Anything, mock.Anything).Return(&dashboardUnstructuredOrg1, nil).Once() + k8sClientMock.searcher.On("GetStats", mock.Anything).Return(&countOrg2, nil).Once() + k8sClientMock.searcher.On("GetStats", mock.Anything).Return(&countOrg1, nil).Once() result, err := service.Count(ctx, query) require.NoError(t, err) @@ -1858,6 +1834,38 @@ func TestQuotaCount(t *testing.T) { }) } +func TestCountDashboardsInOrg(t *testing.T) { + fakeStore := dashboards.FakeDashboardStore{} + defer fakeStore.AssertExpectations(t) + service := &DashboardServiceImpl{ + cfg: setting.NewCfg(), + dashboardStore: &fakeStore, + } + count := resource.ResourceStatsResponse{ + Stats: []*resource.ResourceStatsResponse_Stats{ + { + Count: 3, + }, + }, + } + + t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { + service.features = featuremgmt.WithFeatures() + fakeStore.On("CountInOrg", mock.Anything, mock.Anything).Return(nil, nil).Once() + _, err := service.CountDashboardsInOrg(context.Background(), 1) + require.NoError(t, err) + fakeStore.AssertExpectations(t) + }) + + t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { + ctx, k8sClientMock, _ := setupK8sDashboardTests(service) + k8sClientMock.searcher.On("GetStats", mock.Anything).Return(&count, nil).Once() + result, err := service.CountDashboardsInOrg(ctx, 1) + require.NoError(t, err) + require.Equal(t, result, int64(3)) + }) +} + func TestLegacySaveCommandToUnstructured(t *testing.T) { namespace := "test-namespace" t.Run("successfully converts save command to unstructured", func(t *testing.T) { diff --git a/pkg/services/dashboards/store_mock.go b/pkg/services/dashboards/store_mock.go index 0d1e385f1dc18..070c45e641b40 100644 --- a/pkg/services/dashboards/store_mock.go +++ b/pkg/services/dashboards/store_mock.go @@ -48,6 +48,37 @@ func (_m *FakeDashboardStore) Count(_a0 context.Context, _a1 *quota.ScopeParamet return r0, r1 } +// CountInOrg provides a mock function with given fields: _a0, _a1 +func (_m *FakeDashboardStore) CountInOrg(_a0 context.Context, _a1 int64) (int64, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Count") + } + + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) (int64, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) int64); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(int64) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + + // CountDashboardsInFolders provides a mock function with given fields: ctx, request func (_m *FakeDashboardStore) CountDashboardsInFolders(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error) { ret := _m.Called(ctx, request) diff --git a/pkg/services/stats/statsimpl/stats.go b/pkg/services/stats/statsimpl/stats.go index c8c25319f468f..5e909ef657119 100644 --- a/pkg/services/stats/statsimpl/stats.go +++ b/pkg/services/stats/statsimpl/stats.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" @@ -21,13 +22,14 @@ import ( const activeUserTimeLimit = time.Hour * 24 * 30 const dailyActiveUserTimeLimit = time.Hour * 24 -func ProvideService(cfg *setting.Cfg, db db.DB, dashSvc dashboards.DashboardService, folderSvc folder.Service, orgSvc org.Service) stats.Service { +func ProvideService(cfg *setting.Cfg, db db.DB, dashSvc dashboards.DashboardService, folderSvc folder.Service, orgSvc org.Service, features featuremgmt.FeatureToggles) stats.Service { return &sqlStatsService{ cfg: cfg, db: db, folderSvc: folderSvc, dashSvc: dashSvc, orgSvc: orgSvc, + features: features, } } @@ -35,48 +37,23 @@ type sqlStatsService struct { db db.DB cfg *setting.Cfg dashSvc dashboards.DashboardService + features featuremgmt.FeatureToggles folderSvc folder.Service orgSvc org.Service } -type dashboardStats struct { - count int - bytesTotal int - bytesMax int -} - -func (ss *sqlStatsService) collectDashboardStats(ctx context.Context, orgs []*org.OrgDTO, calculateByteSize bool) (dashboardStats, error) { - stats := dashboardStats{ - count: 0, - bytesTotal: 0, - bytesMax: 0, - } - +func (ss *sqlStatsService) getDashboardCount(ctx context.Context, orgs []*org.OrgDTO) (int64, error) { + count := int64(0) for _, org := range orgs { ctx = identity.WithRequester(ctx, getStatsRequester(org.ID)) - dashs, err := ss.dashSvc.GetAllDashboardsByOrgId(ctx, org.ID) + dashsCount, err := ss.dashSvc.CountDashboardsInOrg(ctx, org.ID) if err != nil { - return stats, err - } - stats.count += len(dashs) - - // only calculate bytes if needed - if calculateByteSize { - for _, dash := range dashs { - b, err := dash.Data.ToDB() - if err != nil { - return stats, err - } - stats.bytesTotal += len(b) - - if len(b) > stats.bytesMax { - stats.bytesMax = len(b) - } - } + return 0, err } + count += dashsCount } - return stats, nil + return count, nil } func (ss *sqlStatsService) getTagCount(ctx context.Context, orgs []*org.OrgDTO) (int64, error) { @@ -172,7 +149,6 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS monthlyActiveUserDeadlineDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) sb.Write(`(SELECT COUNT(*) FROM `+dialect.Quote("user")+` WHERE `+ notServiceAccount(dialect)+` AND last_seen_at > ?) AS monthly_active_users,`, monthlyActiveUserDeadlineDate) - sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_provisioning") + `) AS provisioned_dashboards,`) sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_snapshot") + `) AS snapshots,`) sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_version") + `) AS dashboard_versions,`) @@ -190,6 +166,11 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS if ss.IsUnifiedAlertingEnabled() { sb.Write(`(SELECT COUNT(DISTINCT (` + dialect.Quote("rule_group") + `)) FROM ` + dialect.Quote("alert_rule") + `) AS rule_groups,`) } + // currently not supported when dashboards are in unified storage + if !ss.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) { + sb.Write(`(SELECT SUM(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_total,`, dialect.BooleanStr(false)) + sb.Write(`(SELECT MAX(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_max,`, dialect.BooleanStr(false)) + } sb.Write(ss.roleCounterSQL(ctx)) @@ -216,13 +197,11 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS result.Orgs = int64(len(orgs)) // for services in unified storage, get the stats through the service rather than the db directly - dashStats, err := ss.collectDashboardStats(ctx, orgs, true) + dashCount, err := ss.getDashboardCount(ctx, orgs) if err != nil { return result, err } - result.DashboardBytesMax = int64(dashStats.bytesMax) - result.DashboardBytesTotal = int64(dashStats.bytesTotal) - result.Dashboards = int64(dashStats.count) + result.Dashboards = dashCount folderCount, err := ss.getFolderCount(ctx, orgs) if err != nil { @@ -329,11 +308,11 @@ func (ss *sqlStatsService) GetAdminStats(ctx context.Context, query *stats.GetAd result.Orgs = int64(len(orgs)) // for services in unified storage, get the stats through the service rather than the db directly - dashStats, err := ss.collectDashboardStats(ctx, orgs, false) + dashCount, err := ss.getDashboardCount(ctx, orgs) if err != nil { return result, err } - result.Dashboards = int64(dashStats.count) + result.Dashboards = dashCount tagCount, err := ss.getTagCount(ctx, orgs) if err != nil { diff --git a/pkg/services/stats/statsimpl/stats_test.go b/pkg/services/stats/statsimpl/stats_test.go index 3b3951ed27b28..324a83c678849 100644 --- a/pkg/services/stats/statsimpl/stats_test.go +++ b/pkg/services/stats/statsimpl/stats_test.go @@ -10,12 +10,12 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/correlations" "github.com/grafana/grafana/pkg/services/correlations/correlationstest" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/org" @@ -40,15 +40,9 @@ func TestIntegrationStatsDataAccess(t *testing.T) { db, cfg := db.InitTestDBWithCfg(t) orgSvc := populateDB(t, db, cfg) dashSvc := &dashboards.FakeDashboardService{} - emptyJson := simplejson.New() - emptyJsonBytes, err := emptyJson.ToDB() - require.NoError(t, err) - largerJson := simplejson.NewFromAny(map[string]string{"key": "value"}) - largerJsonBytes, err := largerJson.ToDB() - require.NoError(t, err) - dashSvc.On("GetAllDashboardsByOrgId", mock.Anything, int64(1)).Return([]*dashboards.Dashboard{{Data: largerJson}, {Data: emptyJson}}, nil) - dashSvc.On("GetAllDashboardsByOrgId", mock.Anything, int64(2)).Return([]*dashboards.Dashboard{}, nil) - dashSvc.On("GetAllDashboardsByOrgId", mock.Anything, int64(3)).Return([]*dashboards.Dashboard{}, nil) + dashSvc.On("CountDashboardsInOrg", mock.Anything, int64(1)).Return(int64(2), nil) + dashSvc.On("CountDashboardsInOrg", mock.Anything, int64(2)).Return(int64(1), nil) + dashSvc.On("CountDashboardsInOrg", mock.Anything, int64(3)).Return(int64(0), nil) dashSvc.On("GetDashboardTags", mock.Anything, &dashboards.GetDashboardTagsQuery{OrgID: 1}).Return([]*dashboards.DashboardTagCloudItem{{Term: "test"}}, nil) dashSvc.On("GetDashboardTags", mock.Anything, &dashboards.GetDashboardTagsQuery{OrgID: 2}).Return([]*dashboards.DashboardTagCloudItem{}, nil) dashSvc.On("GetDashboardTags", mock.Anything, &dashboards.GetDashboardTagsQuery{OrgID: 3}).Return([]*dashboards.DashboardTagCloudItem{}, nil) @@ -61,6 +55,7 @@ func TestIntegrationStatsDataAccess(t *testing.T) { dashSvc: dashSvc, orgSvc: orgSvc, folderSvc: folderService, + features: featuremgmt.WithFeatures(), } t.Run("Get system stats should not results in error", func(t *testing.T) { @@ -76,10 +71,8 @@ func TestIntegrationStatsDataAccess(t *testing.T) { assert.Equal(t, int64(0), result.APIKeys) assert.Equal(t, int64(2), result.Correlations) assert.Equal(t, int64(3), result.Orgs) - assert.Equal(t, int64(2), result.Dashboards) + assert.Equal(t, int64(3), result.Dashboards) assert.Equal(t, int64(9), result.Folders) // will return 3 folders for each org - assert.Equal(t, int64(len(largerJsonBytes)+len(emptyJsonBytes)), result.DashboardBytesTotal) - assert.Equal(t, int64(len(largerJsonBytes)), result.DashboardBytesMax) assert.NotNil(t, result.DatabaseCreatedTime) assert.Equal(t, db.GetDialect().DriverName(), result.DatabaseDriver) }) @@ -113,7 +106,7 @@ func TestIntegrationStatsDataAccess(t *testing.T) { stats, err := statsService.GetAdminStats(context.Background(), &query) assert.NoError(t, err) assert.Equal(t, int64(1), stats.Tags) - assert.Equal(t, int64(2), stats.Dashboards) + assert.Equal(t, int64(3), stats.Dashboards) assert.Equal(t, int64(3), stats.Orgs) }) }