mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Move stats service into a standalone packge from sqlstore (#59574)
* move original stats service into a separate package * add stats service to wire * move GetAdminStats * switch to using stats.Service * add missing package * fix api tests
This commit is contained in:
parent
a343defe64
commit
9cdb6b07c7
@ -47,7 +47,7 @@ func (hs *HTTPServer) AdminGetSettings(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) AdminGetStats(c *models.ReqContext) response.Response {
|
||||
statsQuery := models.GetAdminStatsQuery{}
|
||||
|
||||
if err := hs.SQLStore.GetAdminStats(c.Req.Context(), &statsQuery); err != nil {
|
||||
if err := hs.statsService.GetAdminStats(c.Req.Context(), &statsQuery); err != nil {
|
||||
return response.Error(500, "Failed to get admin stats from database", err)
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/searchusers/filters"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/stats/statsimpl"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||
@ -251,6 +252,7 @@ func (s *fakeRenderService) Init() error {
|
||||
|
||||
func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url string, permissions []accesscontrol.Permission) (*scenarioContext, *HTTPServer) {
|
||||
store := sqlstore.InitTestDB(t)
|
||||
statsService := statsimpl.ProvideService(store)
|
||||
hs := &HTTPServer{
|
||||
Cfg: cfg,
|
||||
Live: newTestLive(t, store),
|
||||
@ -262,6 +264,7 @@ func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url strin
|
||||
searchUsersService: searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), usertest.NewUserServiceFake()),
|
||||
ldapGroups: ldap.ProvideGroupsService(),
|
||||
accesscontrolService: actest.FakeService{},
|
||||
statsService: statsService,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
||||
"github.com/grafana/grafana/pkg/services/querylibrary"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2"
|
||||
"github.com/grafana/grafana/pkg/services/stats"
|
||||
"github.com/grafana/grafana/pkg/services/store/object/httpobjectstore"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@ -207,6 +208,7 @@ type HTTPServer struct {
|
||||
annotationsRepo annotations.Repository
|
||||
tagService tag.Service
|
||||
oauthTokenService oauthtoken.OAuthTokenService
|
||||
statsService stats.Service
|
||||
}
|
||||
|
||||
type ServerOptions struct {
|
||||
@ -249,6 +251,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
accesscontrolService accesscontrol.Service, dashboardThumbsService thumbs.DashboardThumbService, navTreeService navtree.Service,
|
||||
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService,
|
||||
queryLibraryHTTPService querylibrary.HTTPService, queryLibraryService querylibrary.Service, oauthTokenService oauthtoken.OAuthTokenService,
|
||||
statsService stats.Service,
|
||||
) (*HTTPServer, error) {
|
||||
web.Env = cfg.Env
|
||||
m := web.New()
|
||||
@ -353,6 +356,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
QueryLibraryHTTPService: queryLibraryHTTPService,
|
||||
QueryLibraryService: queryLibraryService,
|
||||
oauthTokenService: oauthTokenService,
|
||||
statsService: statsService,
|
||||
}
|
||||
if hs.Listener != nil {
|
||||
hs.log.Debug("Using provided listener")
|
||||
|
@ -12,12 +12,14 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/stats/statsimpl"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func TestConcurrentUsersMetrics(t *testing.T) {
|
||||
sqlStore, cfg := db.InitTestDBwithCfg(t)
|
||||
s := createService(t, cfg, sqlStore)
|
||||
statsService := statsimpl.ProvideService(sqlStore)
|
||||
s := createService(t, cfg, sqlStore, statsService)
|
||||
|
||||
createConcurrentTokens(t, sqlStore)
|
||||
|
||||
@ -34,7 +36,8 @@ func TestConcurrentUsersMetrics(t *testing.T) {
|
||||
|
||||
func TestConcurrentUsersStats(t *testing.T) {
|
||||
sqlStore, cfg := db.InitTestDBwithCfg(t)
|
||||
s := createService(t, cfg, sqlStore)
|
||||
statsService := statsimpl.ProvideService(sqlStore)
|
||||
s := createService(t, cfg, sqlStore, statsService)
|
||||
|
||||
createConcurrentTokens(t, sqlStore)
|
||||
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/stats/statstest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -33,10 +34,12 @@ func TestDetectPrometheusVariant(t *testing.T) {
|
||||
t.Cleanup(cortex.Close)
|
||||
|
||||
sqlStore := mockstore.NewSQLStoreMock()
|
||||
statsService := statstest.NewFakeService()
|
||||
s := createService(
|
||||
t,
|
||||
setting.NewCfg(),
|
||||
sqlStore,
|
||||
statsService,
|
||||
withDatasources(mockDatasourceService{datasources: []*datasources.DataSource{
|
||||
{
|
||||
Id: 1,
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/stats"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -28,6 +29,7 @@ type Service struct {
|
||||
plugins plugins.Store
|
||||
social social.Service
|
||||
usageStats usagestats.Service
|
||||
statsService stats.Service
|
||||
features *featuremgmt.FeatureManager
|
||||
datasources datasources.DataSourceService
|
||||
httpClientProvider httpclient.Provider
|
||||
@ -42,6 +44,7 @@ type Service struct {
|
||||
|
||||
func ProvideService(
|
||||
us usagestats.Service,
|
||||
statsService stats.Service,
|
||||
cfg *setting.Cfg,
|
||||
store sqlstore.Store,
|
||||
social social.Service,
|
||||
@ -56,6 +59,7 @@ func ProvideService(
|
||||
plugins: plugins,
|
||||
social: social,
|
||||
usageStats: us,
|
||||
statsService: statsService,
|
||||
features: features,
|
||||
datasources: datasourceService,
|
||||
httpClientProvider: httpClientProvider,
|
||||
@ -106,7 +110,7 @@ func (s *Service) collectSystemStats(ctx context.Context) (map[string]interface{
|
||||
m := map[string]interface{}{}
|
||||
|
||||
statsQuery := models.GetSystemStatsQuery{}
|
||||
if err := s.sqlstore.GetSystemStats(ctx, &statsQuery); err != nil {
|
||||
if err := s.statsService.GetSystemStats(ctx, &statsQuery); err != nil {
|
||||
s.log.Error("Failed to get system stats", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
@ -219,7 +223,7 @@ func (s *Service) collectAlertNotifierStats(ctx context.Context) (map[string]int
|
||||
m := map[string]interface{}{}
|
||||
// get stats about alert notifier usage
|
||||
anStats := models.GetAlertNotifierUsageStatsQuery{}
|
||||
if err := s.sqlstore.GetAlertNotifiersUsageStats(ctx, &anStats); err != nil {
|
||||
if err := s.statsService.GetAlertNotifiersUsageStats(ctx, &anStats); err != nil {
|
||||
s.log.Error("Failed to get alert notification stats", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
@ -233,7 +237,7 @@ func (s *Service) collectAlertNotifierStats(ctx context.Context) (map[string]int
|
||||
func (s *Service) collectDatasourceStats(ctx context.Context) (map[string]interface{}, error) {
|
||||
m := map[string]interface{}{}
|
||||
dsStats := models.GetDataSourceStatsQuery{}
|
||||
if err := s.sqlstore.GetDataSourceStats(ctx, &dsStats); err != nil {
|
||||
if err := s.statsService.GetDataSourceStats(ctx, &dsStats); err != nil {
|
||||
s.log.Error("Failed to get datasource stats", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
@ -280,7 +284,7 @@ func (s *Service) collectDatasourceAccess(ctx context.Context) (map[string]inter
|
||||
|
||||
// fetch datasource access stats
|
||||
dsAccessStats := models.GetDataSourceAccessStatsQuery{}
|
||||
if err := s.sqlstore.GetDataSourceAccessStats(ctx, &dsAccessStats); err != nil {
|
||||
if err := s.statsService.GetDataSourceAccessStats(ctx, &dsAccessStats); err != nil {
|
||||
s.log.Error("Failed to get datasource access stats", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
@ -316,7 +320,7 @@ func (s *Service) updateTotalStats(ctx context.Context) bool {
|
||||
}
|
||||
|
||||
statsQuery := models.GetSystemStatsQuery{}
|
||||
if err := s.sqlstore.GetSystemStats(ctx, &statsQuery); err != nil {
|
||||
if err := s.statsService.GetSystemStats(ctx, &statsQuery); err != nil {
|
||||
s.log.Error("Failed to get system stats", "error", err)
|
||||
return false
|
||||
}
|
||||
@ -351,7 +355,7 @@ func (s *Service) updateTotalStats(ctx context.Context) bool {
|
||||
metrics.MStatTotalPublicDashboards.Set(float64(statsQuery.Result.PublicDashboards))
|
||||
|
||||
dsStats := models.GetDataSourceStatsQuery{}
|
||||
if err := s.sqlstore.GetDataSourceStats(ctx, &dsStats); err != nil {
|
||||
if err := s.statsService.GetDataSourceStats(ctx, &dsStats); err != nil {
|
||||
s.log.Error("Failed to get datasource stats", "error", err)
|
||||
return true
|
||||
}
|
||||
|
@ -24,16 +24,19 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/stats"
|
||||
"github.com/grafana/grafana/pkg/services/stats/statstest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func TestTotalStatsUpdate(t *testing.T) {
|
||||
sqlStore := mockstore.NewSQLStoreMock()
|
||||
s := createService(t, setting.NewCfg(), sqlStore)
|
||||
statsService := statstest.NewFakeService()
|
||||
s := createService(t, setting.NewCfg(), sqlStore, statsService)
|
||||
s.cfg.MetricsEndpointEnabled = true
|
||||
s.cfg.MetricsEndpointDisableTotalStats = false
|
||||
|
||||
sqlStore.ExpectedSystemStats = &models.SystemStats{}
|
||||
statsService.ExpectedSystemStats = &models.SystemStats{}
|
||||
|
||||
tests := []struct {
|
||||
MetricsEndpointEnabled bool
|
||||
@ -94,8 +97,9 @@ func TestUsageStatsProviders(t *testing.T) {
|
||||
provider2 := &dummyUsageStatProvider{stats: map[string]interface{}{"my_stat_x": "valx", "my_stat_z": "valz"}}
|
||||
|
||||
store := mockstore.NewSQLStoreMock()
|
||||
mockSystemStats(store)
|
||||
s := createService(t, setting.NewCfg(), store)
|
||||
statsService := statstest.NewFakeService()
|
||||
mockSystemStats(statsService)
|
||||
s := createService(t, setting.NewCfg(), store, statsService)
|
||||
s.RegisterProviders([]registry.ProvidesUsageStats{provider1, provider2})
|
||||
|
||||
m, err := s.collectAdditionalMetrics(context.Background())
|
||||
@ -109,8 +113,9 @@ func TestUsageStatsProviders(t *testing.T) {
|
||||
|
||||
func TestFeatureUsageStats(t *testing.T) {
|
||||
store := mockstore.NewSQLStoreMock()
|
||||
mockSystemStats(store)
|
||||
s := createService(t, setting.NewCfg(), store)
|
||||
statsService := statstest.NewFakeService()
|
||||
mockSystemStats(statsService)
|
||||
s := createService(t, setting.NewCfg(), store, statsService)
|
||||
|
||||
m, err := s.collectSystemStats(context.Background())
|
||||
require.NoError(t, err, "Expected no error")
|
||||
@ -121,6 +126,7 @@ func TestFeatureUsageStats(t *testing.T) {
|
||||
|
||||
func TestCollectingUsageStats(t *testing.T) {
|
||||
sqlStore := mockstore.NewSQLStoreMock()
|
||||
statsService := statstest.NewFakeService()
|
||||
expectedDataSources := []*datasources.DataSource{
|
||||
{
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -148,12 +154,12 @@ func TestCollectingUsageStats(t *testing.T) {
|
||||
AuthProxyEnabled: true,
|
||||
Packaging: "deb",
|
||||
ReportingDistributor: "hosted-grafana",
|
||||
}, sqlStore,
|
||||
}, sqlStore, statsService,
|
||||
withDatasources(mockDatasourceService{datasources: expectedDataSources}))
|
||||
|
||||
s.startTime = time.Now().Add(-1 * time.Minute)
|
||||
|
||||
mockSystemStats(sqlStore)
|
||||
mockSystemStats(statsService)
|
||||
|
||||
createConcurrentTokens(t, sqlStore)
|
||||
|
||||
@ -203,6 +209,7 @@ func TestCollectingUsageStats(t *testing.T) {
|
||||
|
||||
func TestElasticStats(t *testing.T) {
|
||||
sqlStore := mockstore.NewSQLStoreMock()
|
||||
statsService := statstest.NewFakeService()
|
||||
|
||||
expectedDataSources := []*datasources.DataSource{
|
||||
{
|
||||
@ -231,7 +238,7 @@ func TestElasticStats(t *testing.T) {
|
||||
AuthProxyEnabled: true,
|
||||
Packaging: "deb",
|
||||
ReportingDistributor: "hosted-grafana",
|
||||
}, sqlStore,
|
||||
}, sqlStore, statsService,
|
||||
withDatasources(mockDatasourceService{datasources: expectedDataSources}))
|
||||
|
||||
metrics, err := s.collectElasticStats(context.Background())
|
||||
@ -242,11 +249,12 @@ func TestElasticStats(t *testing.T) {
|
||||
}
|
||||
func TestDatasourceStats(t *testing.T) {
|
||||
sqlStore := mockstore.NewSQLStoreMock()
|
||||
s := createService(t, &setting.Cfg{}, sqlStore)
|
||||
statsService := statstest.NewFakeService()
|
||||
s := createService(t, &setting.Cfg{}, sqlStore, statsService)
|
||||
|
||||
setupSomeDataSourcePlugins(t, s)
|
||||
|
||||
sqlStore.ExpectedDataSourceStats = []*models.DataSourceStats{
|
||||
statsService.ExpectedDataSourceStats = []*models.DataSourceStats{
|
||||
{
|
||||
Type: datasources.DS_ES,
|
||||
Count: 9,
|
||||
@ -283,7 +291,7 @@ func TestDatasourceStats(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
sqlStore.ExpectedDataSourcesAccessStats = []*models.DataSourceAccessStats{
|
||||
statsService.ExpectedDataSourcesAccessStats = []*models.DataSourceAccessStats{
|
||||
{
|
||||
Type: datasources.DS_ES,
|
||||
Access: "direct",
|
||||
@ -349,9 +357,10 @@ func TestDatasourceStats(t *testing.T) {
|
||||
|
||||
func TestAlertNotifiersStats(t *testing.T) {
|
||||
sqlStore := mockstore.NewSQLStoreMock()
|
||||
s := createService(t, &setting.Cfg{}, sqlStore)
|
||||
statsService := statstest.NewFakeService()
|
||||
s := createService(t, &setting.Cfg{}, sqlStore, statsService)
|
||||
|
||||
sqlStore.ExpectedNotifierUsageStats = []*models.NotifierUsageStats{
|
||||
statsService.ExpectedNotifierUsageStats = []*models.NotifierUsageStats{
|
||||
{
|
||||
Type: "slack",
|
||||
Count: 1,
|
||||
@ -369,8 +378,8 @@ func TestAlertNotifiersStats(t *testing.T) {
|
||||
assert.EqualValues(t, 2, metrics["stats.alert_notifiers.webhook.count"])
|
||||
}
|
||||
|
||||
func mockSystemStats(sqlStore *mockstore.SQLStoreMock) {
|
||||
sqlStore.ExpectedSystemStats = &models.SystemStats{
|
||||
func mockSystemStats(statsService *statstest.FakeService) {
|
||||
statsService.ExpectedSystemStats = &models.SystemStats{
|
||||
Dashboards: 1,
|
||||
Datasources: 2,
|
||||
Users: 3,
|
||||
@ -437,7 +446,7 @@ func setupSomeDataSourcePlugins(t *testing.T, s *Service) {
|
||||
}
|
||||
}
|
||||
|
||||
func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store, opts ...func(*serviceOptions)) *Service {
|
||||
func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store, statsService stats.Service, opts ...func(*serviceOptions)) *Service {
|
||||
t.Helper()
|
||||
|
||||
o := &serviceOptions{datasources: mockDatasourceService{}}
|
||||
@ -448,6 +457,7 @@ func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store, opts ..
|
||||
|
||||
return ProvideService(
|
||||
&usagestats.UsageStatsMock{},
|
||||
statsService,
|
||||
cfg,
|
||||
store,
|
||||
&mockSocial{},
|
||||
|
@ -124,6 +124,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/star/starimpl"
|
||||
"github.com/grafana/grafana/pkg/services/stats/statsimpl"
|
||||
"github.com/grafana/grafana/pkg/services/store"
|
||||
"github.com/grafana/grafana/pkg/services/store/kind"
|
||||
"github.com/grafana/grafana/pkg/services/store/object/httpobjectstore"
|
||||
@ -353,6 +354,7 @@ var wireBasicSet = wire.NewSet(
|
||||
publicdashboardsApi.ProvideApi,
|
||||
userimpl.ProvideService,
|
||||
orgimpl.ProvideService,
|
||||
statsimpl.ProvideService,
|
||||
grpccontext.ProvideContextHandler,
|
||||
grpcserver.ProvideService,
|
||||
grpcserver.ProvideHealthService,
|
||||
|
@ -1,20 +0,0 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIntegration_GetAdminStats(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
sqlStore := InitTestDB(t)
|
||||
|
||||
query := models.GetAdminStatsQuery{}
|
||||
err := sqlStore.GetAdminStats(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
}
|
@ -12,13 +12,8 @@ import (
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error
|
||||
GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error
|
||||
GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error
|
||||
GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error
|
||||
GetDialect() migrator.Dialect
|
||||
GetDBType() core.DbType
|
||||
GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error
|
||||
CreateUser(ctx context.Context, cmd user.CreateUserCommand) (*user.User, error)
|
||||
WithDbSession(ctx context.Context, callback DBTransactionFunc) error
|
||||
WithNewDbSession(ctx context.Context, callback DBTransactionFunc) error
|
||||
|
16
pkg/services/stats/stats.go
Normal file
16
pkg/services/stats/stats.go
Normal file
@ -0,0 +1,16 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error
|
||||
GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error
|
||||
GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error
|
||||
GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error
|
||||
GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error
|
||||
GetSystemUserCountStats(ctx context.Context, query *models.GetSystemUserCountStatsQuery) error
|
||||
}
|
@ -1,39 +1,48 @@
|
||||
package sqlstore
|
||||
package statsimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/stats"
|
||||
)
|
||||
|
||||
const activeUserTimeLimit = time.Hour * 24 * 30
|
||||
const dailyActiveUserTimeLimit = time.Hour * 24
|
||||
|
||||
func (ss *SQLStore) GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error {
|
||||
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
|
||||
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + dialect.Quote("alert_notification") + ` GROUP BY type`
|
||||
func ProvideService(db db.DB) stats.Service {
|
||||
return &sqlStatsService{db: db}
|
||||
}
|
||||
|
||||
type sqlStatsService struct{ db db.DB }
|
||||
|
||||
func (ss *sqlStatsService) GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + ss.db.GetDialect().Quote("alert_notification") + ` GROUP BY type`
|
||||
query.Result = make([]*models.NotifierUsageStats, 0)
|
||||
err := dbSession.SQL(rawSQL).Find(&query.Result)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error {
|
||||
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
|
||||
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + dialect.Quote("data_source") + ` GROUP BY type`
|
||||
func (ss *sqlStatsService) GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + ss.db.GetDialect().Quote("data_source") + ` GROUP BY type`
|
||||
query.Result = make([]*models.DataSourceStats, 0)
|
||||
err := dbSession.SQL(rawSQL).Find(&query.Result)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error {
|
||||
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
|
||||
var rawSQL = `SELECT COUNT(*) AS count, type, access FROM ` + dialect.Quote("data_source") + ` GROUP BY type, access`
|
||||
func (ss *sqlStatsService) GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
var rawSQL = `SELECT COUNT(*) AS count, type, access FROM ` + ss.db.GetDialect().Quote("data_source") + ` GROUP BY type, access`
|
||||
query.Result = make([]*models.DataSourceAccessStats, 0)
|
||||
err := dbSession.SQL(rawSQL).Find(&query.Result)
|
||||
return err
|
||||
@ -45,10 +54,11 @@ func notServiceAccount(dialect migrator.Dialect) string {
|
||||
dialect.BooleanStr(false)
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error {
|
||||
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
|
||||
sb := &SQLBuilder{}
|
||||
func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
sb := &sqlstore.SQLBuilder{}
|
||||
sb.Write("SELECT ")
|
||||
dialect := ss.db.GetDialect()
|
||||
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` WHERE ` + notServiceAccount(dialect) + `) AS users,`)
|
||||
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("org") + `) AS orgs,`)
|
||||
sb.Write(`(SELECT COUNT(*) FROM ` + dialect.Quote("data_source") + `) AS datasources,`)
|
||||
@ -88,10 +98,10 @@ func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemS
|
||||
WHERE d.is_folder = ?
|
||||
) AS folder_permissions,`, dialect.BooleanStr(true))
|
||||
|
||||
sb.Write(viewersPermissionsCounterSQL("dashboards_viewers_can_edit", false, models.PERMISSION_EDIT))
|
||||
sb.Write(viewersPermissionsCounterSQL("dashboards_viewers_can_admin", false, models.PERMISSION_ADMIN))
|
||||
sb.Write(viewersPermissionsCounterSQL("folders_viewers_can_edit", true, models.PERMISSION_EDIT))
|
||||
sb.Write(viewersPermissionsCounterSQL("folders_viewers_can_admin", true, models.PERMISSION_ADMIN))
|
||||
sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_edit", false, models.PERMISSION_EDIT))
|
||||
sb.Write(viewersPermissionsCounterSQL(ss.db, "dashboards_viewers_can_admin", false, models.PERMISSION_ADMIN))
|
||||
sb.Write(viewersPermissionsCounterSQL(ss.db, "folders_viewers_can_edit", true, models.PERMISSION_EDIT))
|
||||
sb.Write(viewersPermissionsCounterSQL(ss.db, "folders_viewers_can_admin", true, models.PERMISSION_ADMIN))
|
||||
|
||||
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,`)
|
||||
@ -112,7 +122,7 @@ func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemS
|
||||
sb.Write(ss.roleCounterSQL(ctx))
|
||||
|
||||
var stats models.SystemStats
|
||||
_, err := dbSession.SQL(sb.GetSQLString(), sb.params...).Get(&stats)
|
||||
_, err := dbSession.SQL(sb.GetSQLString(), sb.GetParams()...).Get(&stats)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -123,7 +133,7 @@ func (ss *SQLStore) GetSystemStats(ctx context.Context, query *models.GetSystemS
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) roleCounterSQL(ctx context.Context) string {
|
||||
func (ss *sqlStatsService) roleCounterSQL(ctx context.Context) string {
|
||||
const roleCounterTimeout = 20 * time.Second
|
||||
ctx, cancel := context.WithTimeout(ctx, roleCounterTimeout)
|
||||
defer cancel()
|
||||
@ -142,7 +152,8 @@ func (ss *SQLStore) roleCounterSQL(ctx context.Context) string {
|
||||
return sqlQuery
|
||||
}
|
||||
|
||||
func viewersPermissionsCounterSQL(statName string, isFolder bool, permission models.PermissionType) string {
|
||||
func viewersPermissionsCounterSQL(db db.DB, statName string, isFolder bool, permission models.PermissionType) string {
|
||||
dialect := db.GetDialect()
|
||||
return `(
|
||||
SELECT COUNT(*)
|
||||
FROM ` + dialect.Quote("dashboard_acl") + ` AS acl
|
||||
@ -154,8 +165,9 @@ func viewersPermissionsCounterSQL(statName string, isFolder bool, permission mod
|
||||
) AS ` + statName + `, `
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error {
|
||||
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
|
||||
func (ss *sqlStatsService) GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
dialect := ss.db.GetDialect()
|
||||
now := time.Now()
|
||||
activeEndDate := now.Add(-activeUserTimeLimit)
|
||||
dailyActiveEndDate := now.Add(-dailyActiveUserTimeLimit)
|
||||
@ -231,9 +243,9 @@ func (ss *SQLStore) GetAdminStats(ctx context.Context, query *models.GetAdminSta
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetSystemUserCountStats(ctx context.Context, query *models.GetSystemUserCountStatsQuery) error {
|
||||
return ss.WithDbSession(ctx, func(sess *DBSession) error {
|
||||
var rawSQL = `SELECT COUNT(id) AS Count FROM ` + dialect.Quote("user")
|
||||
func (ss *sqlStatsService) GetSystemUserCountStats(ctx context.Context, query *models.GetSystemUserCountStatsQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
var rawSQL = `SELECT COUNT(id) AS Count FROM ` + ss.db.GetDialect().Quote("user")
|
||||
var stats models.SystemUserCountStats
|
||||
_, err := sess.SQL(rawSQL).Get(&stats)
|
||||
if err != nil {
|
||||
@ -246,7 +258,7 @@ func (ss *SQLStore) GetSystemUserCountStats(ctx context.Context, query *models.G
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) updateUserRoleCountsIfNecessary(ctx context.Context, forced bool) error {
|
||||
func (ss *sqlStatsService) updateUserRoleCountsIfNecessary(ctx context.Context, forced bool) error {
|
||||
memoizationPeriod := time.Now().Add(-userStatsCacheLimetime)
|
||||
if forced || userStatsCache.memoized.Before(memoizationPeriod) {
|
||||
err := ss.updateUserRoleCounts(ctx)
|
||||
@ -270,8 +282,8 @@ var (
|
||||
userStatsCacheLimetime = 5 * time.Minute
|
||||
)
|
||||
|
||||
func (ss *SQLStore) updateUserRoleCounts(ctx context.Context) error {
|
||||
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
|
||||
func (ss *sqlStatsService) updateUserRoleCounts(ctx context.Context) error {
|
||||
return ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
query := `
|
||||
SELECT role AS bitrole, active, COUNT(role) AS count FROM
|
||||
(SELECT last_seen_at>? AS active, last_seen_at>? AS daily_active, SUM(role) AS role
|
||||
@ -283,7 +295,7 @@ SELECT role AS bitrole, active, COUNT(role) AS count FROM
|
||||
ELSE 1
|
||||
END AS role,
|
||||
u.last_seen_at
|
||||
FROM ` + dialect.Quote("user") + ` AS u INNER JOIN org_user ON org_user.user_id = u.id
|
||||
FROM ` + ss.db.GetDialect().Quote("user") + ` AS u INNER JOIN org_user ON org_user.user_id = u.id
|
||||
GROUP BY u.id, u.last_seen_at, org_user.role) AS t2
|
||||
GROUP BY id, last_seen_at) AS t1
|
||||
GROUP BY active, daily_active, role;`
|
@ -1,28 +1,31 @@
|
||||
package sqlstore
|
||||
package statsimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIntegrationStatsDataAccess(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
sqlStore := InitTestDB(t)
|
||||
populateDB(t, sqlStore)
|
||||
db := sqlstore.InitTestDB(t)
|
||||
statsService := &sqlStatsService{db: db}
|
||||
populateDB(t, db)
|
||||
|
||||
t.Run("Get system stats should not results in error", func(t *testing.T) {
|
||||
query := models.GetSystemStatsQuery{}
|
||||
err := sqlStore.GetSystemStats(context.Background(), &query)
|
||||
err := statsService.GetSystemStats(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), query.Result.Users)
|
||||
assert.Equal(t, int64(0), query.Result.Editors)
|
||||
@ -35,38 +38,40 @@ func TestIntegrationStatsDataAccess(t *testing.T) {
|
||||
|
||||
t.Run("Get system user count stats should not results in error", func(t *testing.T) {
|
||||
query := models.GetSystemUserCountStatsQuery{}
|
||||
err := sqlStore.GetSystemUserCountStats(context.Background(), &query)
|
||||
err := statsService.GetSystemUserCountStats(context.Background(), &query)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get datasource stats should not results in error", func(t *testing.T) {
|
||||
query := models.GetDataSourceStatsQuery{}
|
||||
err := sqlStore.GetDataSourceStats(context.Background(), &query)
|
||||
err := statsService.GetDataSourceStats(context.Background(), &query)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get datasource access stats should not results in error", func(t *testing.T) {
|
||||
query := models.GetDataSourceAccessStatsQuery{}
|
||||
err := sqlStore.GetDataSourceAccessStats(context.Background(), &query)
|
||||
err := statsService.GetDataSourceAccessStats(context.Background(), &query)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get alert notifier stats should not results in error", func(t *testing.T) {
|
||||
query := models.GetAlertNotifierUsageStatsQuery{}
|
||||
err := sqlStore.GetAlertNotifiersUsageStats(context.Background(), &query)
|
||||
err := statsService.GetAlertNotifiersUsageStats(context.Background(), &query)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get admin stats should not result in error", func(t *testing.T) {
|
||||
query := models.GetAdminStatsQuery{}
|
||||
err := sqlStore.GetAdminStats(context.Background(), &query)
|
||||
err := statsService.GetAdminStats(context.Background(), &query)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func populateDB(t *testing.T, sqlStore *SQLStore) {
|
||||
func populateDB(t *testing.T, sqlStore *sqlstore.SQLStore) {
|
||||
t.Helper()
|
||||
|
||||
orgService, _ := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotatest.New(false, nil))
|
||||
|
||||
users := make([]user.User, 3)
|
||||
for i := range users {
|
||||
cmd := user.CreateUserCommand{
|
||||
@ -81,33 +86,41 @@ func populateDB(t *testing.T, sqlStore *SQLStore) {
|
||||
}
|
||||
|
||||
// add 2nd user as editor
|
||||
cmd := &models.AddOrgUserCommand{
|
||||
OrgId: users[0].OrgID,
|
||||
UserId: users[1].ID,
|
||||
cmd := &org.AddOrgUserCommand{
|
||||
OrgID: users[0].OrgID,
|
||||
UserID: users[1].ID,
|
||||
Role: org.RoleEditor,
|
||||
}
|
||||
err := sqlStore.addOrgUser(context.Background(), cmd)
|
||||
err := orgService.AddOrgUser(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
// add 3rd user as viewer
|
||||
cmd = &models.AddOrgUserCommand{
|
||||
OrgId: users[0].OrgID,
|
||||
UserId: users[2].ID,
|
||||
cmd = &org.AddOrgUserCommand{
|
||||
OrgID: users[0].OrgID,
|
||||
UserID: users[2].ID,
|
||||
Role: org.RoleViewer,
|
||||
}
|
||||
err = sqlStore.addOrgUser(context.Background(), cmd)
|
||||
err = orgService.AddOrgUser(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
// add 1st user as admin
|
||||
cmd = &models.AddOrgUserCommand{
|
||||
OrgId: users[1].OrgID,
|
||||
UserId: users[0].ID,
|
||||
cmd = &org.AddOrgUserCommand{
|
||||
OrgID: users[1].OrgID,
|
||||
UserID: users[0].ID,
|
||||
Role: org.RoleAdmin,
|
||||
}
|
||||
err = sqlStore.addOrgUser(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
// force renewal of user stats
|
||||
err = sqlStore.updateUserRoleCountsIfNecessary(context.Background(), true)
|
||||
err = orgService.AddOrgUser(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIntegration_GetAdminStats(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
db := sqlstore.InitTestDB(t)
|
||||
statsService := ProvideService(db)
|
||||
|
||||
query := models.GetAdminStatsQuery{}
|
||||
err := statsService.GetAdminStats(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
}
|
48
pkg/services/stats/statstest/stats.go
Normal file
48
pkg/services/stats/statstest/stats.go
Normal file
@ -0,0 +1,48 @@
|
||||
package statstest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type FakeService struct {
|
||||
ExpectedSystemStats *models.SystemStats
|
||||
ExpectedDataSourceStats []*models.DataSourceStats
|
||||
ExpectedDataSourcesAccessStats []*models.DataSourceAccessStats
|
||||
ExpectedNotifierUsageStats []*models.NotifierUsageStats
|
||||
|
||||
ExpectedError error
|
||||
}
|
||||
|
||||
func NewFakeService() *FakeService {
|
||||
return &FakeService{}
|
||||
}
|
||||
|
||||
func (s *FakeService) GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error {
|
||||
return s.ExpectedError
|
||||
}
|
||||
|
||||
func (s *FakeService) GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error {
|
||||
query.Result = s.ExpectedNotifierUsageStats
|
||||
return s.ExpectedError
|
||||
}
|
||||
|
||||
func (s *FakeService) GetDataSourceStats(ctx context.Context, query *models.GetDataSourceStatsQuery) error {
|
||||
query.Result = s.ExpectedDataSourceStats
|
||||
return s.ExpectedError
|
||||
}
|
||||
|
||||
func (s *FakeService) GetDataSourceAccessStats(ctx context.Context, query *models.GetDataSourceAccessStatsQuery) error {
|
||||
query.Result = s.ExpectedDataSourcesAccessStats
|
||||
return s.ExpectedError
|
||||
}
|
||||
|
||||
func (s *FakeService) GetSystemStats(ctx context.Context, query *models.GetSystemStatsQuery) error {
|
||||
query.Result = s.ExpectedSystemStats
|
||||
return s.ExpectedError
|
||||
}
|
||||
|
||||
func (s *FakeService) GetSystemUserCountStats(ctx context.Context, query *models.GetSystemUserCountStatsQuery) error {
|
||||
return s.ExpectedError
|
||||
}
|
Loading…
Reference in New Issue
Block a user