Dashboard Alert Extractor: Create service for dashboard extractor and remove bus (#45518)

* Create DashAlertService service

* Remove no used dashboard service from plugin's manager that generates dependency cycle in Enterprise

* Remove bus for dashboard permissions

* Remove bus from dashboard extractor service

* Add missing argument

* Fix wire

* Fix lint

* More goimports

* Use datasource service instead sql calls

* Fix integration test
pull/45636/head^2
Selene 3 years ago committed by GitHub
parent 1df040eb23
commit 2c90dcf3c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      pkg/api/common_test.go
  2. 2
      pkg/api/dashboard_permission_test.go
  3. 4
      pkg/api/dashboard_test.go
  4. 13
      pkg/api/datasources_test.go
  5. 2
      pkg/api/folder_permission_test.go
  6. 5
      pkg/api/http_server.go
  7. 6
      pkg/plugins/manager/dashboards_test.go
  8. 36
      pkg/plugins/manager/manager.go
  9. 3
      pkg/plugins/manager/manager_integration_test.go
  10. 9
      pkg/plugins/manager/manager_test.go
  11. 2
      pkg/server/wire.go
  12. 6
      pkg/server/wireexts_oss.go
  13. 41
      pkg/services/alerting/engine.go
  14. 2
      pkg/services/alerting/engine_integration_test.go
  15. 2
      pkg/services/alerting/engine_test.go
  16. 86
      pkg/services/alerting/extractor.go
  17. 150
      pkg/services/alerting/extractor_test.go
  18. 7
      pkg/services/alerting/models.go
  19. 9
      pkg/services/alerting/test_rule.go
  20. 75
      pkg/services/dashboards/manager/dashboard_service.go
  21. 28
      pkg/services/dashboards/manager/dashboard_service_integration_test.go
  22. 5
      pkg/services/dashboards/manager/dashboard_service_test.go
  23. 2
      pkg/services/dashboards/manager/folder_service_test.go
  24. 2
      pkg/services/datasources/permissions/datasource_permissions.go
  25. 8
      pkg/services/datasources/permissions/datasource_permissions_mocks.go
  26. 8
      pkg/services/libraryelements/libraryelements_test.go
  27. 8
      pkg/services/librarypanels/librarypanels_test.go
  28. 2
      pkg/services/ngalert/tests/util.go
  29. 1
      pkg/services/sqlstore/mockstore/mockstore.go

@ -364,7 +364,7 @@ func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessCont
RouteRegister: routeRegister, RouteRegister: routeRegister,
SQLStore: db, SQLStore: db,
searchUsersService: searchusers.ProvideUsersService(bus, filters.ProvideOSSSearchUserFilter()), searchUsersService: searchusers.ProvideUsersService(bus, filters.ProvideOSSSearchUserFilter()),
dashboardService: dashboardservice.ProvideDashboardService(dashboardsStore), dashboardService: dashboardservice.ProvideDashboardService(dashboardsStore, nil),
} }
// Defining the accesscontrol service has to be done before registering routes // Defining the accesscontrol service has to be done before registering routes

@ -29,7 +29,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
hs := &HTTPServer{ hs := &HTTPServer{
Cfg: settings, Cfg: settings,
dashboardService: dashboardservice.ProvideDashboardService(dashboardStore), dashboardService: dashboardservice.ProvideDashboardService(dashboardStore, nil),
SQLStore: mockSQLStore, SQLStore: mockSQLStore,
} }

@ -219,7 +219,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
Live: newTestLive(t), Live: newTestLive(t),
LibraryPanelService: &mockLibraryPanelService{}, LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{}, LibraryElementService: &mockLibraryElementService{},
dashboardService: service.ProvideDashboardService(dashboardStore), dashboardService: service.ProvideDashboardService(dashboardStore, nil),
SQLStore: mockSQLStore, SQLStore: mockSQLStore,
} }
hs.SQLStore = mockSQLStore hs.SQLStore = mockSQLStore
@ -939,7 +939,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
LibraryPanelService: &libraryPanelsService, LibraryPanelService: &libraryPanelsService,
LibraryElementService: &libraryElementsService, LibraryElementService: &libraryElementsService,
ProvisioningService: provisioningService, ProvisioningService: provisioningService,
dashboardProvisioningService: service.ProvideDashboardService(dashboardStore), dashboardProvisioningService: service.ProvideDashboardService(dashboardStore, nil),
SQLStore: sc.sqlStore, SQLStore: sc.sqlStore,
} }

@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -29,7 +30,7 @@ const (
func TestDataSourcesProxy_userLoggedIn(t *testing.T) { func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
mockSQLStore := mockstore.NewSQLStoreMock() mockSQLStore := mockstore.NewSQLStoreMock()
mockDatasourcePermissionService := newMockDatasourcePermissionService() mockDatasourcePermissionService := permissions.NewMockDatasourcePermissionService()
loggedInUserScenario(t, "When calling GET on", "/api/datasources/", "/api/datasources/", func(sc *scenarioContext) { loggedInUserScenario(t, "When calling GET on", "/api/datasources/", "/api/datasources/", func(sc *scenarioContext) {
// Stubs the database query // Stubs the database query
ds := []*models.DataSource{ ds := []*models.DataSource{
@ -38,7 +39,7 @@ func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
{Name: "BBB"}, {Name: "BBB"},
{Name: "aaa"}, {Name: "aaa"},
} }
mockDatasourcePermissionService.dsResult = ds mockDatasourcePermissionService.DsResult = ds
// handler func being tested // handler func being tested
hs := &HTTPServer{ hs := &HTTPServer{
@ -209,8 +210,8 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
dsServiceMock := &dataSourcesServiceMock{ dsServiceMock := &dataSourcesServiceMock{
expectedDatasource: &testDatasource, expectedDatasource: &testDatasource,
} }
dsPermissionService := newMockDatasourcePermissionService() dsPermissionService := permissions.NewMockDatasourcePermissionService()
dsPermissionService.dsResult = []*models.DataSource{ dsPermissionService.DsResult = []*models.DataSource{
&testDatasource, &testDatasource,
} }
@ -505,9 +506,9 @@ func TestAPI_Datasources_AccessControl(t *testing.T) {
// mock sqlStore and datasource permission service // mock sqlStore and datasource permission service
dsServiceMock.expectedError = test.expectedSQLError dsServiceMock.expectedError = test.expectedSQLError
dsServiceMock.expectedDatasource = test.expectedDS dsServiceMock.expectedDatasource = test.expectedDS
dsPermissionService.dsResult = []*models.DataSource{test.expectedDS} dsPermissionService.DsResult = []*models.DataSource{test.expectedDS}
if test.expectedDS == nil { if test.expectedDS == nil {
dsPermissionService.dsResult = nil dsPermissionService.DsResult = nil
} }
hs.DataSourcesService = dsServiceMock hs.DataSourcesService = dsServiceMock
hs.DatasourcePermissionsService = dsPermissionService hs.DatasourcePermissionsService = dsPermissionService

@ -30,7 +30,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
dashboardStore := &database.FakeDashboardStore{} dashboardStore := &database.FakeDashboardStore{}
defer dashboardStore.AssertExpectations(t) defer dashboardStore.AssertExpectations(t)
hs := &HTTPServer{Cfg: settings, folderService: folderService, dashboardService: service.ProvideDashboardService(dashboardStore)} hs := &HTTPServer{Cfg: settings, folderService: folderService, dashboardService: service.ProvideDashboardService(dashboardStore, nil)}
t.Run("Given folder not exists", func(t *testing.T) { t.Run("Given folder not exists", func(t *testing.T) {
folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrFolderNotFound).Twice() folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrFolderNotFound).Twice()

@ -36,6 +36,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/dashboardsnapshots"
"github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/permissions"
"github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/hooks"
@ -135,7 +136,7 @@ type HTTPServer struct {
dashboardService dashboards.DashboardService dashboardService dashboards.DashboardService
dashboardProvisioningService dashboards.DashboardProvisioningService dashboardProvisioningService dashboards.DashboardProvisioningService
folderService dashboards.FolderService folderService dashboards.FolderService
DatasourcePermissionsService DatasourcePermissionsService DatasourcePermissionsService permissions.DatasourcePermissionsService
commentsService *comments.Service commentsService *comments.Service
AlertNotificationService *alerting.AlertNotificationService AlertNotificationService *alerting.AlertNotificationService
DashboardsnapshotsService *dashboardsnapshots.Service DashboardsnapshotsService *dashboardsnapshots.Service
@ -169,7 +170,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
authInfoService login.AuthInfoService, permissionsServices accesscontrol.PermissionsServices, authInfoService login.AuthInfoService, permissionsServices accesscontrol.PermissionsServices,
notificationService *notifications.NotificationService, dashboardService dashboards.DashboardService, notificationService *notifications.NotificationService, dashboardService dashboards.DashboardService,
dashboardProvisioningService dashboards.DashboardProvisioningService, folderService dashboards.FolderService, dashboardProvisioningService dashboards.DashboardProvisioningService, folderService dashboards.FolderService,
datasourcePermissionsService DatasourcePermissionsService, alertNotificationService *alerting.AlertNotificationService, datasourcePermissionsService permissions.DatasourcePermissionsService, alertNotificationService *alerting.AlertNotificationService,
dashboardsnapshotsService *dashboardsnapshots.Service, commentsService *comments.Service, pluginSettings *pluginsettings.ServiceImpl, dashboardsnapshotsService *dashboardsnapshots.Service, commentsService *comments.Service, pluginSettings *pluginsettings.ServiceImpl,
) (*HTTPServer, error) { ) (*HTTPServer, error) {
web.Env = cfg.Env web.Env = cfg.Env

@ -11,9 +11,6 @@ import (
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider" "github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
"github.com/grafana/grafana/pkg/plugins/manager/loader" "github.com/grafana/grafana/pkg/plugins/manager/loader"
"github.com/grafana/grafana/pkg/plugins/manager/signature" "github.com/grafana/grafana/pkg/plugins/manager/signature"
"github.com/grafana/grafana/pkg/services/dashboards/database"
service "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -27,9 +24,8 @@ func TestGetPluginDashboards(t *testing.T) {
}, },
} }
pmCfg := plugins.FromGrafanaCfg(cfg) pmCfg := plugins.FromGrafanaCfg(cfg)
dashboardService := service.ProvideDashboardService(database.ProvideDashboardStore(&sqlstore.SQLStore{}))
pm, err := ProvideService(cfg, loader.New(pmCfg, nil, pm, err := ProvideService(cfg, loader.New(pmCfg, nil,
signature.NewUnsignedAuthorizer(pmCfg), &provider.Service{}), dashboardService) signature.NewUnsignedAuthorizer(pmCfg), &provider.Service{}))
require.NoError(t, err) require.NoError(t, err)
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error {

@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation" "github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
"github.com/grafana/grafana/pkg/plugins/manager/installer" "github.com/grafana/grafana/pkg/plugins/manager/installer"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
) )
@ -30,38 +29,35 @@ var _ plugins.StaticRouteResolver = (*PluginManager)(nil)
var _ plugins.RendererManager = (*PluginManager)(nil) var _ plugins.RendererManager = (*PluginManager)(nil)
type PluginManager struct { type PluginManager struct {
cfg *plugins.Cfg cfg *plugins.Cfg
store map[string]*plugins.Plugin store map[string]*plugins.Plugin
pluginInstaller plugins.Installer pluginInstaller plugins.Installer
pluginLoader plugins.Loader pluginLoader plugins.Loader
pluginsMu sync.RWMutex pluginsMu sync.RWMutex
pluginPaths map[plugins.Class][]string pluginPaths map[plugins.Class][]string
dashboardService dashboards.DashboardService log log.Logger
log log.Logger
} }
func ProvideService(grafanaCfg *setting.Cfg, pluginLoader plugins.Loader, dashboardService dashboards.DashboardService) (*PluginManager, error) { func ProvideService(grafanaCfg *setting.Cfg, pluginLoader plugins.Loader) (*PluginManager, error) {
pm := New(plugins.FromGrafanaCfg(grafanaCfg), map[plugins.Class][]string{ pm := New(plugins.FromGrafanaCfg(grafanaCfg), map[plugins.Class][]string{
plugins.Core: corePluginPaths(grafanaCfg), plugins.Core: corePluginPaths(grafanaCfg),
plugins.Bundled: {grafanaCfg.BundledPluginsPath}, plugins.Bundled: {grafanaCfg.BundledPluginsPath},
plugins.External: append([]string{grafanaCfg.PluginsPath}, pluginSettingPaths(grafanaCfg)...), plugins.External: append([]string{grafanaCfg.PluginsPath}, pluginSettingPaths(grafanaCfg)...),
}, pluginLoader, dashboardService) }, pluginLoader)
if err := pm.Init(); err != nil { if err := pm.Init(); err != nil {
return nil, err return nil, err
} }
return pm, nil return pm, nil
} }
func New(cfg *plugins.Cfg, pluginPaths map[plugins.Class][]string, pluginLoader plugins.Loader, func New(cfg *plugins.Cfg, pluginPaths map[plugins.Class][]string, pluginLoader plugins.Loader) *PluginManager {
dashboardService dashboards.DashboardService) *PluginManager {
return &PluginManager{ return &PluginManager{
cfg: cfg, cfg: cfg,
pluginLoader: pluginLoader, pluginLoader: pluginLoader,
pluginPaths: pluginPaths, pluginPaths: pluginPaths,
store: make(map[string]*plugins.Plugin), store: make(map[string]*plugins.Plugin),
log: log.New("plugin.manager"), log: log.New("plugin.manager"),
pluginInstaller: installer.New(false, cfg.BuildVersion, newInstallerLogger("plugin.installer", true)), pluginInstaller: installer.New(false, cfg.BuildVersion, newInstallerLogger("plugin.installer", true)),
dashboardService: dashboardService,
} }
} }

@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider" "github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
"github.com/grafana/grafana/pkg/plugins/manager/loader" "github.com/grafana/grafana/pkg/plugins/manager/loader"
"github.com/grafana/grafana/pkg/plugins/manager/signature" "github.com/grafana/grafana/pkg/plugins/manager/signature"
service "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/services/searchV2" "github.com/grafana/grafana/pkg/services/searchV2"
@ -94,7 +93,7 @@ func TestPluginManager_int_init(t *testing.T) {
pmCfg := plugins.FromGrafanaCfg(cfg) pmCfg := plugins.FromGrafanaCfg(cfg)
pm, err := ProvideService(cfg, loader.New(pmCfg, license, signature.NewUnsignedAuthorizer(pmCfg), pm, err := ProvideService(cfg, loader.New(pmCfg, license, signature.NewUnsignedAuthorizer(pmCfg),
provider.ProvideService(coreRegistry)), &service.DashboardServiceImpl{}) provider.ProvideService(coreRegistry)))
require.NoError(t, err) require.NoError(t, err)
verifyCorePluginCatalogue(t, pm) verifyCorePluginCatalogue(t, pm)

@ -12,9 +12,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/services/dashboards/database"
service "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -469,8 +466,7 @@ func TestPluginManager_lifecycle_unmanaged(t *testing.T) {
func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager { func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager {
t.Helper() t.Helper()
dashboardService := service.ProvideDashboardService(database.ProvideDashboardStore(&sqlstore.SQLStore{})) pm := New(&plugins.Cfg{}, nil, &fakeLoader{})
pm := New(&plugins.Cfg{}, nil, &fakeLoader{}, dashboardService)
for _, cb := range cbs { for _, cb := range cbs {
cb(pm) cb(pm)
@ -524,8 +520,7 @@ func newScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *managerS
cfg.Azure.ManagedIdentityClientId = "client-id" cfg.Azure.ManagedIdentityClientId = "client-id"
loader := &fakeLoader{} loader := &fakeLoader{}
dashboardService := service.ProvideDashboardService(database.ProvideDashboardStore(&sqlstore.SQLStore{})) manager := New(cfg, nil, loader)
manager := New(cfg, nil, loader, dashboardService)
manager.pluginLoader = loader manager.pluginLoader = loader
ctx := &managerScenarioCtx{ ctx := &managerScenarioCtx{
manager: manager, manager: manager,

@ -213,6 +213,8 @@ var wireBasicSet = wire.NewSet(
dashboardimportservice.ProvideService, dashboardimportservice.ProvideService,
wire.Bind(new(dashboardimport.Service), new(*dashboardimportservice.ImportDashboardService)), wire.Bind(new(dashboardimport.Service), new(*dashboardimportservice.ImportDashboardService)),
plugindashboards.ProvideService, plugindashboards.ProvideService,
alerting.ProvideDashAlertExtractorService,
wire.Bind(new(alerting.DashAlertExtractor), new(*alerting.DashAlertExtractorService)),
comments.ProvideService, comments.ProvideService,
) )

@ -5,7 +5,6 @@ package server
import ( import (
"github.com/google/wire" "github.com/google/wire"
"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider" "github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
@ -18,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/permissions"
datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service" datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service"
"github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/encryption/ossencryption"
@ -75,8 +75,8 @@ var wireExtsBasicSet = wire.NewSet(
wire.Bind(new(kmsproviders.Service), new(osskmsproviders.Service)), wire.Bind(new(kmsproviders.Service), new(osskmsproviders.Service)),
ldap.ProvideGroupsService, ldap.ProvideGroupsService,
wire.Bind(new(ldap.Groups), new(*ldap.OSSGroups)), wire.Bind(new(ldap.Groups), new(*ldap.OSSGroups)),
api.ProvideDatasourcePermissionsService, permissions.ProvideDatasourcePermissionsService,
wire.Bind(new(api.DatasourcePermissionsService), new(*api.OSSDatasourcePermissionsService)), wire.Bind(new(permissions.DatasourcePermissionsService), new(*permissions.OSSDatasourcePermissionsService)),
ossaccesscontrol.ProvidePermissionsServices, ossaccesscontrol.ProvidePermissionsServices,
wire.Bind(new(accesscontrol.PermissionsServices), new(*ossaccesscontrol.PermissionsService)), wire.Bind(new(accesscontrol.PermissionsServices), new(*ossaccesscontrol.PermissionsService)),
) )

@ -45,16 +45,17 @@ type AlertEngine struct {
DataService legacydata.RequestHandler DataService legacydata.RequestHandler
Cfg *setting.Cfg Cfg *setting.Cfg
execQueue chan *Job execQueue chan *Job
ticker *Ticker ticker *Ticker
scheduler scheduler scheduler scheduler
evalHandler evalHandler evalHandler evalHandler
ruleReader ruleReader ruleReader ruleReader
log log.Logger log log.Logger
resultHandler resultHandler resultHandler resultHandler
usageStatsService usagestats.Service usageStatsService usagestats.Service
tracer tracing.Tracer tracer tracing.Tracer
sqlStore AlertStore sqlStore AlertStore
dashAlertExtractor DashAlertExtractor
} }
// IsDisabled returns true if the alerting service is disabled for this instance. // IsDisabled returns true if the alerting service is disabled for this instance.
@ -65,16 +66,18 @@ func (e *AlertEngine) IsDisabled() bool {
// ProvideAlertEngine returns a new AlertEngine. // ProvideAlertEngine returns a new AlertEngine.
func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidator models.PluginRequestValidator, func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidator models.PluginRequestValidator,
dataService legacydata.RequestHandler, usageStatsService usagestats.Service, encryptionService encryption.Internal, dataService legacydata.RequestHandler, usageStatsService usagestats.Service, encryptionService encryption.Internal,
notificationService *notifications.NotificationService, tracer tracing.Tracer, sqlStore AlertStore, cfg *setting.Cfg) *AlertEngine { notificationService *notifications.NotificationService, tracer tracing.Tracer, sqlStore AlertStore, cfg *setting.Cfg,
dashAlertExtractor DashAlertExtractor) *AlertEngine {
e := &AlertEngine{ e := &AlertEngine{
Cfg: cfg, Cfg: cfg,
RenderService: renderer, RenderService: renderer,
Bus: bus, Bus: bus,
RequestValidator: requestValidator, RequestValidator: requestValidator,
DataService: dataService, DataService: dataService,
usageStatsService: usageStatsService, usageStatsService: usageStatsService,
tracer: tracer, tracer: tracer,
sqlStore: sqlStore, sqlStore: sqlStore,
dashAlertExtractor: dashAlertExtractor,
} }
e.ticker = NewTicker(time.Now(), time.Second*0, clock.New(), 1) e.ticker = NewTicker(time.Now(), time.Second*0, clock.New(), 1)
e.execQueue = make(chan *Job, 1000) e.execQueue = make(chan *Job, 1000)

@ -24,7 +24,7 @@ func TestEngineTimeouts(t *testing.T) {
usMock := &usagestats.UsageStatsMock{T: t} usMock := &usagestats.UsageStatsMock{T: t}
tracer, err := tracing.InitializeTracerForTest() tracer, err := tracing.InitializeTracerForTest()
require.NoError(t, err) require.NoError(t, err)
engine := ProvideAlertEngine(nil, nil, nil, nil, usMock, ossencryption.ProvideService(), nil, tracer, nil, setting.NewCfg()) engine := ProvideAlertEngine(nil, nil, nil, nil, usMock, ossencryption.ProvideService(), nil, tracer, nil, setting.NewCfg(), nil)
setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingNotificationTimeout = 30 * time.Second
setting.AlertingMaxAttempts = 3 setting.AlertingMaxAttempts = 3
engine.resultHandler = &FakeResultHandler{} engine.resultHandler = &FakeResultHandler{}

@ -102,7 +102,7 @@ func TestEngineProcessJob(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
store := &AlertStoreMock{} store := &AlertStoreMock{}
engine := ProvideAlertEngine(nil, bus, nil, nil, usMock, ossencryption.ProvideService(), nil, tracer, store, setting.NewCfg()) engine := ProvideAlertEngine(nil, bus, nil, nil, usMock, ossencryption.ProvideService(), nil, tracer, store, setting.NewCfg(), nil)
setting.AlertingEvaluationTimeout = 30 * time.Second setting.AlertingEvaluationTimeout = 30 * time.Second
setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingNotificationTimeout = 30 * time.Second
setting.AlertingMaxAttempts = 3 setting.AlertingMaxAttempts = 3

@ -6,31 +6,34 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/permissions"
) )
// DashAlertExtractor extracts alerts from the dashboard json. type DashAlertExtractor interface {
type DashAlertExtractor struct { GetAlerts(ctx context.Context, dashAlertInfo DashAlertInfo) ([]*models.Alert, error)
User *models.SignedInUser ValidateAlerts(ctx context.Context, dashAlertInfo DashAlertInfo) error
Dash *models.Dashboard
OrgID int64
log log.Logger
} }
// NewDashAlertExtractor returns a new DashAlertExtractor. // DashAlertExtractorService extracts alerts from the dashboard json.
func NewDashAlertExtractor(dash *models.Dashboard, orgID int64, user *models.SignedInUser) *DashAlertExtractor { type DashAlertExtractorService struct {
return &DashAlertExtractor{ datasourcePermissionsService permissions.DatasourcePermissionsService
User: user, datasourceService datasources.DataSourceService
Dash: dash, log log.Logger
OrgID: orgID, }
log: log.New("alerting.extractor"),
func ProvideDashAlertExtractorService(datasourcePermissionsService permissions.DatasourcePermissionsService, datasourceService datasources.DataSourceService) *DashAlertExtractorService {
return &DashAlertExtractorService{
datasourcePermissionsService: datasourcePermissionsService,
datasourceService: datasourceService,
log: log.New("alerting.extractor"),
} }
} }
func (e *DashAlertExtractor) lookupQueryDataSource(ctx context.Context, panel *simplejson.Json, panelQuery *simplejson.Json) (*models.DataSource, error) { func (e *DashAlertExtractorService) lookupQueryDataSource(ctx context.Context, panel *simplejson.Json, panelQuery *simplejson.Json, orgID int64) (*models.DataSource, error) {
dsName := "" dsName := ""
dsUid := "" dsUid := ""
@ -47,15 +50,15 @@ func (e *DashAlertExtractor) lookupQueryDataSource(ctx context.Context, panel *s
} }
if dsName == "" && dsUid == "" { if dsName == "" && dsUid == "" {
query := &models.GetDefaultDataSourceQuery{OrgId: e.OrgID} query := &models.GetDefaultDataSourceQuery{OrgId: orgID}
if err := bus.Dispatch(ctx, query); err != nil { if err := e.datasourceService.GetDefaultDataSource(ctx, query); err != nil {
return nil, err return nil, err
} }
return query.Result, nil return query.Result, nil
} }
query := &models.GetDataSourceQuery{Name: dsName, Uid: dsUid, OrgId: e.OrgID} query := &models.GetDataSourceQuery{Name: dsName, Uid: dsUid, OrgId: orgID}
if err := bus.Dispatch(ctx, query); err != nil { if err := e.datasourceService.GetDataSource(ctx, query); err != nil {
return nil, err return nil, err
} }
@ -101,7 +104,7 @@ func UAEnabled(ctx context.Context) bool {
return enabled return enabled
} }
func (e *DashAlertExtractor) getAlertFromPanels(ctx context.Context, jsonWithPanels *simplejson.Json, validateAlertFunc func(*models.Alert) bool, logTranslationFailures bool) ([]*models.Alert, error) { func (e *DashAlertExtractorService) getAlertFromPanels(ctx context.Context, jsonWithPanels *simplejson.Json, validateAlertFunc func(*models.Alert) bool, logTranslationFailures bool, dashAlertInfo DashAlertInfo) ([]*models.Alert, error) {
alerts := make([]*models.Alert, 0) alerts := make([]*models.Alert, 0)
for _, panelObj := range jsonWithPanels.Get("panels").MustArray() { for _, panelObj := range jsonWithPanels.Get("panels").MustArray() {
@ -111,7 +114,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(ctx context.Context, jsonWithPan
// check if the panel is collapsed // check if the panel is collapsed
if collapsed && collapsedJSON.MustBool() { if collapsed && collapsedJSON.MustBool() {
// extract alerts from sub panels for collapsed panels // extract alerts from sub panels for collapsed panels
alertSlice, err := e.getAlertFromPanels(ctx, panel, validateAlertFunc, logTranslationFailures) alertSlice, err := e.getAlertFromPanels(ctx, panel, validateAlertFunc, logTranslationFailures, dashAlertInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -143,8 +146,8 @@ func (e *DashAlertExtractor) getAlertFromPanels(ctx context.Context, jsonWithPan
Err: validationErr.Err, Err: validationErr.Err,
PanelID: panelID, PanelID: panelID,
} }
if e.Dash != nil { if dashAlertInfo.Dash != nil {
ve.DashboardID = e.Dash.Id ve.DashboardID = dashAlertInfo.Dash.Id
} }
return ve return ve
} }
@ -170,8 +173,8 @@ func (e *DashAlertExtractor) getAlertFromPanels(ctx context.Context, jsonWithPan
} }
alert := &models.Alert{ alert := &models.Alert{
DashboardId: e.Dash.Id, DashboardId: dashAlertInfo.Dash.Id,
OrgId: e.OrgID, OrgId: dashAlertInfo.OrgID,
PanelId: panelID, PanelId: panelID,
Id: jsonAlert.Get("id").MustInt64(), Id: jsonAlert.Get("id").MustInt64(),
Name: jsonAlert.Get("name").MustString(), Name: jsonAlert.Get("name").MustString(),
@ -198,24 +201,21 @@ func (e *DashAlertExtractor) getAlertFromPanels(ctx context.Context, jsonWithPan
return nil, ValidationError{Reason: reason} return nil, ValidationError{Reason: reason}
} }
datasource, err := e.lookupQueryDataSource(ctx, panel, panelQuery) datasource, err := e.lookupQueryDataSource(ctx, panel, panelQuery, dashAlertInfo.OrgID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dsFilterQuery := models.DatasourcesPermissionFilterQuery{ dsFilterQuery := models.DatasourcesPermissionFilterQuery{
User: e.User, User: dashAlertInfo.User,
Datasources: []*models.DataSource{datasource}, Datasources: []*models.DataSource{datasource},
} }
if err := bus.Dispatch(ctx, &dsFilterQuery); err != nil { if err := e.datasourcePermissionsService.FilterDatasourcesBasedOnQueryPermissions(ctx, &dsFilterQuery); err != nil {
if !errors.Is(err, bus.ErrHandlerNotFound) { return nil, err
return nil, err }
} if len(dsFilterQuery.Result) == 0 {
} else { return nil, models.ErrDataSourceAccessDenied
if len(dsFilterQuery.Result) == 0 {
return nil, models.ErrDataSourceAccessDenied
}
} }
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id) jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
@ -250,12 +250,12 @@ func validateAlertRule(alert *models.Alert) bool {
} }
// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data. // GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data.
func (e *DashAlertExtractor) GetAlerts(ctx context.Context) ([]*models.Alert, error) { func (e *DashAlertExtractorService) GetAlerts(ctx context.Context, dashAlertInfo DashAlertInfo) ([]*models.Alert, error) {
return e.extractAlerts(ctx, validateAlertRule, true) return e.extractAlerts(ctx, validateAlertRule, true, dashAlertInfo)
} }
func (e *DashAlertExtractor) extractAlerts(ctx context.Context, validateFunc func(alert *models.Alert) bool, logTranslationFailures bool) ([]*models.Alert, error) { func (e *DashAlertExtractorService) extractAlerts(ctx context.Context, validateFunc func(alert *models.Alert) bool, logTranslationFailures bool, dashAlertInfo DashAlertInfo) ([]*models.Alert, error) {
dashboardJSON, err := copyJSON(e.Dash.Data) dashboardJSON, err := copyJSON(dashAlertInfo.Dash.Data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -268,7 +268,7 @@ func (e *DashAlertExtractor) extractAlerts(ctx context.Context, validateFunc fun
if len(rows) > 0 { if len(rows) > 0 {
for _, rowObj := range rows { for _, rowObj := range rows {
row := simplejson.NewFromAny(rowObj) row := simplejson.NewFromAny(rowObj)
a, err := e.getAlertFromPanels(ctx, row, validateFunc, logTranslationFailures) a, err := e.getAlertFromPanels(ctx, row, validateFunc, logTranslationFailures, dashAlertInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -276,7 +276,7 @@ func (e *DashAlertExtractor) extractAlerts(ctx context.Context, validateFunc fun
alerts = append(alerts, a...) alerts = append(alerts, a...)
} }
} else { } else {
a, err := e.getAlertFromPanels(ctx, dashboardJSON, validateFunc, logTranslationFailures) a, err := e.getAlertFromPanels(ctx, dashboardJSON, validateFunc, logTranslationFailures, dashAlertInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -290,9 +290,9 @@ func (e *DashAlertExtractor) extractAlerts(ctx context.Context, validateFunc fun
// ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id // ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id
// in the first validation pass. // in the first validation pass.
func (e *DashAlertExtractor) ValidateAlerts(ctx context.Context) error { func (e *DashAlertExtractorService) ValidateAlerts(ctx context.Context, dashAlertInfo DashAlertInfo) error {
_, err := e.extractAlerts(ctx, func(alert *models.Alert) bool { _, err := e.extractAlerts(ctx, func(alert *models.Alert) bool {
return alert.OrgId != 0 && alert.PanelId != 0 return alert.OrgId != 0 && alert.PanelId != 0
}, false) }, false, dashAlertInfo)
return err return err
} }

@ -6,9 +6,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -21,40 +22,24 @@ func TestAlertRuleExtraction(t *testing.T) {
// mock data // mock data
defaultDs := &models.DataSource{Id: 12, OrgId: 1, Name: "I am default", IsDefault: true, Uid: "def-uid"} defaultDs := &models.DataSource{Id: 12, OrgId: 1, Name: "I am default", IsDefault: true, Uid: "def-uid"}
graphite2Ds := &models.DataSource{Id: 15, OrgId: 1, Name: "graphite2", Uid: "graphite2-uid"} graphite2Ds := &models.DataSource{Id: 15, OrgId: 1, Name: "graphite2", Uid: "graphite2-uid"}
influxDBDs := &models.DataSource{Id: 16, OrgId: 1, Name: "InfluxDB", Uid: "InfluxDB-uid"}
prom := &models.DataSource{Id: 17, OrgId: 1, Name: "Prometheus", Uid: "Prometheus-uid"}
bus.AddHandler("test", func(ctx context.Context, query *models.GetDefaultDataSourceQuery) error {
query.Result = defaultDs
return nil
})
bus.AddHandler("test", func(ctx context.Context, query *models.GetDataSourceQuery) error {
if query.Name == defaultDs.Name || query.Uid == defaultDs.Uid {
query.Result = defaultDs
}
if query.Name == graphite2Ds.Name || query.Uid == graphite2Ds.Uid {
query.Result = graphite2Ds
}
if query.Name == influxDBDs.Name || query.Uid == influxDBDs.Uid {
query.Result = influxDBDs
}
if query.Name == prom.Name || query.Uid == prom.Uid {
query.Result = prom
}
return nil
})
json, err := ioutil.ReadFile("./testdata/graphite-alert.json") json, err := ioutil.ReadFile("./testdata/graphite-alert.json")
require.Nil(t, err) require.Nil(t, err)
dsPermissions := permissions.NewMockDatasourcePermissionService()
dsPermissions.DsResult = []*models.DataSource{
{
Id: 1,
},
}
dsService := &fakeDatasourceService{ExpectedDatasource: defaultDs}
extractor := ProvideDashAlertExtractorService(dsPermissions, dsService)
t.Run("Parsing alert rules from dashboard json", func(t *testing.T) { t.Run("Parsing alert rules from dashboard json", func(t *testing.T) {
dashJSON, err := simplejson.NewJson(json) dashJSON, err := simplejson.NewJson(json)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
getTarget := func(j *simplejson.Json) string { getTarget := func(j *simplejson.Json) string {
rowObj := j.Get("rows").MustArray()[0] rowObj := j.Get("rows").MustArray()[0]
row := simplejson.NewFromAny(rowObj) row := simplejson.NewFromAny(rowObj)
@ -67,8 +52,11 @@ func TestAlertRuleExtraction(t *testing.T) {
require.Equal(t, getTarget(dashJSON), "") require.Equal(t, getTarget(dashJSON), "")
extractor := NewDashAlertExtractor(dash, 1, nil) _, _ = extractor.GetAlerts(context.Background(), DashAlertInfo{
_, _ = extractor.GetAlerts(context.Background()) User: nil,
Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
})
require.Equal(t, getTarget(dashJSON), "") require.Equal(t, getTarget(dashJSON), "")
}) })
@ -77,10 +65,12 @@ func TestAlertRuleExtraction(t *testing.T) {
dashJSON, err := simplejson.NewJson(json) dashJSON, err := simplejson.NewJson(json)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON) dsService.ExpectedDatasource = &models.DataSource{Id: 12}
extractor := NewDashAlertExtractor(dash, 1, nil) alerts, err := extractor.GetAlerts(context.Background(), DashAlertInfo{
User: nil,
alerts, err := extractor.GetAlerts(context.Background()) Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
})
require.Nil(t, err) require.Nil(t, err)
@ -127,10 +117,12 @@ func TestAlertRuleExtraction(t *testing.T) {
dashJSON, err := simplejson.NewJson(panelWithoutID) dashJSON, err := simplejson.NewJson(panelWithoutID)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
_, err = extractor.GetAlerts(context.Background()) _, err = extractor.GetAlerts(context.Background(), DashAlertInfo{
User: nil,
Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
})
require.NotNil(t, err) require.NotNil(t, err)
}) })
@ -141,10 +133,12 @@ func TestAlertRuleExtraction(t *testing.T) {
dashJSON, err := simplejson.NewJson(panelWithIDZero) dashJSON, err := simplejson.NewJson(panelWithIDZero)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
_, err = extractor.GetAlerts(context.Background()) _, err = extractor.GetAlerts(context.Background(), DashAlertInfo{
User: nil,
Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
})
require.NotNil(t, err) require.NotNil(t, err)
}) })
@ -154,10 +148,12 @@ func TestAlertRuleExtraction(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
dashJSON, err := simplejson.NewJson(panelWithQuery) dashJSON, err := simplejson.NewJson(panelWithQuery)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
_, err = extractor.GetAlerts(WithUAEnabled(context.Background(), true)) _, err = extractor.GetAlerts(WithUAEnabled(context.Background(), true), DashAlertInfo{
User: nil,
Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
})
require.Equal(t, "alert validation error: Alert on PanelId: 2 refers to query(B) that cannot be found. Legacy alerting queries are not able to be removed at this time in order to preserve the ability to rollback to previous versions of Grafana", err.Error()) require.Equal(t, "alert validation error: Alert on PanelId: 2 refers to query(B) that cannot be found. Legacy alerting queries are not able to be removed at this time in order to preserve the ability to rollback to previous versions of Grafana", err.Error())
}) })
@ -167,10 +163,13 @@ func TestAlertRuleExtraction(t *testing.T) {
dashJSON, err := simplejson.NewJson(panelWithoutSpecifiedDatasource) dashJSON, err := simplejson.NewJson(panelWithoutSpecifiedDatasource)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
alerts, err := extractor.GetAlerts(context.Background()) dsService.ExpectedDatasource = &models.DataSource{Id: 12}
alerts, err := extractor.GetAlerts(context.Background(), DashAlertInfo{
User: nil,
Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
})
require.Nil(t, err) require.Nil(t, err)
condition := simplejson.NewFromAny(alerts[0].Settings.Get("conditions").MustArray()[0]) condition := simplejson.NewFromAny(alerts[0].Settings.Get("conditions").MustArray()[0])
@ -184,10 +183,12 @@ func TestAlertRuleExtraction(t *testing.T) {
dashJSON, err := simplejson.NewJson(json) dashJSON, err := simplejson.NewJson(json)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
alerts, err := extractor.GetAlerts(context.Background()) alerts, err := extractor.GetAlerts(context.Background(), DashAlertInfo{
User: nil,
Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
})
require.Nil(t, err) require.Nil(t, err)
require.Len(t, alerts, 2) require.Len(t, alerts, 2)
@ -209,10 +210,12 @@ func TestAlertRuleExtraction(t *testing.T) {
dashJSON, err := simplejson.NewJson(json) dashJSON, err := simplejson.NewJson(json)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
alerts, err := extractor.GetAlerts(context.Background()) alerts, err := extractor.GetAlerts(context.Background(), DashAlertInfo{
User: nil,
Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
})
require.Nil(t, err) require.Nil(t, err)
require.Len(t, alerts, 1) require.Len(t, alerts, 1)
@ -235,9 +238,12 @@ func TestAlertRuleExtraction(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON) dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
alerts, err := extractor.GetAlerts(context.Background()) alerts, err := extractor.GetAlerts(context.Background(), DashAlertInfo{
User: nil,
Dash: dash,
OrgID: 1,
})
require.Nil(t, err) require.Nil(t, err)
require.Len(t, alerts, 4) require.Len(t, alerts, 4)
@ -249,14 +255,17 @@ func TestAlertRuleExtraction(t *testing.T) {
dashJSON, err := simplejson.NewJson(json) dashJSON, err := simplejson.NewJson(json)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
err = extractor.ValidateAlerts(context.Background()) dashAlertInfo := DashAlertInfo{
User: nil,
Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
}
err = extractor.ValidateAlerts(context.Background(), dashAlertInfo)
require.Nil(t, err) require.Nil(t, err)
_, err = extractor.GetAlerts(context.Background()) _, err = extractor.GetAlerts(context.Background(), dashAlertInfo)
require.Equal(t, err.Error(), "alert validation error: Panel id is not correct, alertName=Influxdb, panelId=1") require.Equal(t, err.Error(), "alert validation error: Panel id is not correct, alertName=Influxdb, panelId=1")
}) })
@ -266,14 +275,18 @@ func TestAlertRuleExtraction(t *testing.T) {
dashJSON, err := simplejson.NewJson(json) dashJSON, err := simplejson.NewJson(json)
require.Nil(t, err) require.Nil(t, err)
dash := models.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1, nil)
err = extractor.ValidateAlerts(context.Background()) dsService.ExpectedDatasource = graphite2Ds
dashAlertInfo := DashAlertInfo{
User: nil,
Dash: models.NewDashboardFromJson(dashJSON),
OrgID: 1,
}
err = extractor.ValidateAlerts(context.Background(), dashAlertInfo)
require.Nil(t, err) require.Nil(t, err)
alerts, err := extractor.GetAlerts(context.Background()) alerts, err := extractor.GetAlerts(context.Background(), dashAlertInfo)
require.Nil(t, err) require.Nil(t, err)
condition := simplejson.NewFromAny(alerts[0].Settings.Get("conditions").MustArray()[0]) condition := simplejson.NewFromAny(alerts[0].Settings.Get("conditions").MustArray()[0])
@ -281,3 +294,18 @@ func TestAlertRuleExtraction(t *testing.T) {
require.EqualValues(t, 15, query.Get("datasourceId").MustInt64()) require.EqualValues(t, 15, query.Get("datasourceId").MustInt64())
}) })
} }
type fakeDatasourceService struct {
ExpectedDatasource *models.DataSource
datasources.DataSourceService
}
func (f *fakeDatasourceService) GetDefaultDataSource(ctx context.Context, query *models.GetDefaultDataSourceQuery) error {
query.Result = f.ExpectedDatasource
return nil
}
func (f *fakeDatasourceService) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error {
query.Result = f.ExpectedDatasource
return nil
}

@ -4,6 +4,7 @@ import (
"sync" "sync"
"github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/models"
) )
// Job holds state about when the alert rule should be evaluated. // Job holds state about when the alert rule should be evaluated.
@ -42,3 +43,9 @@ type EvalMatch struct {
Metric string `json:"metric"` Metric string `json:"metric"`
Tags map[string]string `json:"tags"` Tags map[string]string `json:"tags"`
} }
type DashAlertInfo struct {
User *models.SignedInUser
Dash *models.Dashboard
OrgID int64
}

@ -11,9 +11,12 @@ import (
// AlertTest makes a test alert. // AlertTest makes a test alert.
func (e *AlertEngine) AlertTest(orgID int64, dashboard *simplejson.Json, panelID int64, user *models.SignedInUser) (*EvalContext, error) { func (e *AlertEngine) AlertTest(orgID int64, dashboard *simplejson.Json, panelID int64, user *models.SignedInUser) (*EvalContext, error) {
dash := models.NewDashboardFromJson(dashboard) dash := models.NewDashboardFromJson(dashboard)
dashInfo := DashAlertInfo{
extractor := NewDashAlertExtractor(dash, orgID, user) User: user,
alerts, err := extractor.GetAlerts(context.Background()) Dash: dash,
OrgID: orgID,
}
alerts, err := e.dashAlertExtractor.GetAlerts(context.Background(), dashInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -19,14 +19,16 @@ import (
) )
type DashboardServiceImpl struct { type DashboardServiceImpl struct {
dashboardStore m.Store dashboardStore m.Store
log log.Logger dashAlertExtractor alerting.DashAlertExtractor
log log.Logger
} }
func ProvideDashboardService(store m.Store) *DashboardServiceImpl { func ProvideDashboardService(store m.Store, dashAlertExtractor alerting.DashAlertExtractor) *DashboardServiceImpl {
return &DashboardServiceImpl{ return &DashboardServiceImpl{
dashboardStore: store, dashboardStore: store,
log: log.New("dashboard-service"), dashAlertExtractor: dashAlertExtractor,
log: log.New("dashboard-service"),
} }
} }
@ -74,7 +76,8 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
} }
if shouldValidateAlerts { if shouldValidateAlerts {
if err := validateAlerts(ctx, dash, dto.User); err != nil { dashAlertInfo := alerting.DashAlertInfo{Dash: dash, User: dto.User, OrgID: dash.OrgId}
if err := dr.dashAlertExtractor.ValidateAlerts(ctx, dashAlertInfo); err != nil {
return nil, err return nil, err
} }
} }
@ -139,11 +142,6 @@ func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.
return dr.dashboardStore.DeleteOrphanedProvisionedDashboards(ctx, cmd) return dr.dashboardStore.DeleteOrphanedProvisionedDashboards(ctx, cmd)
} }
var validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
extractor := alerting.NewDashAlertExtractor(dash, dash.OrgId, user)
return extractor.ValidateAlerts(ctx)
}
func validateDashboardRefreshInterval(dash *models.Dashboard) error { func validateDashboardRefreshInterval(dash *models.Dashboard) error {
if setting.MinRefreshInterval == "" { if setting.MinRefreshInterval == "" {
return nil return nil
@ -171,19 +169,6 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error {
return nil return nil
} }
// UpdateAlerting updates alerting.
//
// Stubbable by tests.
var UpdateAlerting = func(ctx context.Context, store m.Store, orgID int64, dashboard *models.Dashboard, user *models.SignedInUser) error {
extractor := alerting.NewDashAlertExtractor(dashboard, orgID, user)
alerts, err := extractor.GetAlerts(ctx)
if err != nil {
return err
}
return store.SaveAlerts(ctx, dashboard.Id, alerts)
}
func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dto *m.SaveDashboardDTO, func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dto *m.SaveDashboardDTO,
provisioning *models.DashboardProvisioning) (*models.Dashboard, error) { provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil { if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil {
@ -210,7 +195,19 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
} }
// alerts // alerts
if err := UpdateAlerting(ctx, dr.dashboardStore, dto.OrgId, dash, dto.User); err != nil { dashAlertInfo := alerting.DashAlertInfo{
User: dto.User,
Dash: dash,
OrgID: dto.OrgId,
}
alerts, err := dr.dashAlertExtractor.GetAlerts(ctx, dashAlertInfo)
if err != nil {
return nil, err
}
err = dr.dashboardStore.SaveAlerts(ctx, dash.Id, alerts)
if err != nil {
return nil, err return nil, err
} }
@ -232,7 +229,19 @@ func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C
return nil, err return nil, err
} }
if err := UpdateAlerting(ctx, dr.dashboardStore, dto.OrgId, dash, dto.User); err != nil { dashAlertInfo := alerting.DashAlertInfo{
User: dto.User,
Dash: dash,
OrgID: dto.OrgId,
}
alerts, err := dr.dashAlertExtractor.GetAlerts(ctx, dashAlertInfo)
if err != nil {
return nil, err
}
err = dr.dashboardStore.SaveAlerts(ctx, dash.Id, alerts)
if err != nil {
return nil, err return nil, err
} }
@ -258,7 +267,19 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *m.SaveDa
return nil, fmt.Errorf("saving dashboard failed: %w", err) return nil, fmt.Errorf("saving dashboard failed: %w", err)
} }
if err := UpdateAlerting(ctx, dr.dashboardStore, dto.OrgId, dash, dto.User); err != nil { dashAlertInfo := alerting.DashAlertInfo{
User: dto.User,
Dash: dash,
OrgID: dto.OrgId,
}
alerts, err := dr.dashAlertExtractor.GetAlerts(ctx, dashAlertInfo)
if err != nil {
return nil, err
}
err = dr.dashboardStore.SaveAlerts(ctx, dash.Id, alerts)
if err != nil {
return nil, err return nil, err
} }

@ -5,6 +5,7 @@ package service
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/services/alerting"
"testing" "testing"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
@ -21,14 +22,6 @@ const testOrgID int64 = 1
func TestIntegratedDashboardService(t *testing.T) { func TestIntegratedDashboardService(t *testing.T) {
t.Run("Given saved folders and dashboards in organization A", func(t *testing.T) { t.Run("Given saved folders and dashboards in organization A", func(t *testing.T) {
origUpdateAlerting := UpdateAlerting
t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
})
UpdateAlerting = func(ctx context.Context, store dashbboardservice.Store, orgID int64, dashboard *models.Dashboard, user *models.SignedInUser) error {
return nil
}
// Basic validation tests // Basic validation tests
permissionScenario(t, "When saving a dashboard with non-existing id", true, permissionScenario(t, "When saving a dashboard with non-existing id", true,
@ -861,7 +854,7 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore
dto := toSaveDashboardDto(cmd) dto := toSaveDashboardDto(cmd)
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
res, err := ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), &dto, false) res, err := ProvideDashboardService(dashboardStore, &dummyDashAlertExtractor{}).SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err) require.NoError(t, err)
return res return res
@ -870,7 +863,7 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore
func callSaveWithError(cmd models.SaveDashboardCommand, sqlStore *sqlstore.SQLStore) error { func callSaveWithError(cmd models.SaveDashboardCommand, sqlStore *sqlstore.SQLStore) error {
dto := toSaveDashboardDto(cmd) dto := toSaveDashboardDto(cmd)
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
_, err := ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), &dto, false) _, err := ProvideDashboardService(dashboardStore, &dummyDashAlertExtractor{}).SaveDashboard(context.Background(), &dto, false)
return err return err
} }
@ -897,7 +890,7 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto
} }
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
res, err := ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), &dto, false) res, err := ProvideDashboardService(dashboardStore, &dummyDashAlertExtractor{}).SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err) require.NoError(t, err)
return res return res
@ -925,7 +918,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore.
} }
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
res, err := ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), &dto, false) res, err := ProvideDashboardService(dashboardStore, &dummyDashAlertExtractor{}).SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err) require.NoError(t, err)
return res return res
@ -942,3 +935,14 @@ func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashbboardservice.SaveD
Overwrite: cmd.Overwrite, Overwrite: cmd.Overwrite,
} }
} }
type dummyDashAlertExtractor struct {
}
func (d *dummyDashAlertExtractor) GetAlerts(ctx context.Context, dashAlertInfo alerting.DashAlertInfo) ([]*models.Alert, error) {
return nil, nil
}
func (d *dummyDashAlertExtractor) ValidateAlerts(ctx context.Context, dashAlertInfo alerting.DashAlertInfo) error {
return nil
}

@ -27,8 +27,9 @@ func TestDashboardService(t *testing.T) {
fakeStore := database.FakeDashboardStore{} fakeStore := database.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t) defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{ service := &DashboardServiceImpl{
log: log.New("test.logger"), log: log.New("test.logger"),
dashboardStore: &fakeStore, dashboardStore: &fakeStore,
dashAlertExtractor: &dummyDashAlertExtractor{},
} }
origNewDashboardGuardian := guardian.New origNewDashboardGuardian := guardian.New

@ -25,7 +25,7 @@ func TestFolderService(t *testing.T) {
store := &database.FakeDashboardStore{} store := &database.FakeDashboardStore{}
defer store.AssertExpectations(t) defer store.AssertExpectations(t)
service := ProvideFolderService( service := ProvideFolderService(
&dashboards.FakeDashboardService{DashboardService: ProvideDashboardService(store)}, &dashboards.FakeDashboardService{DashboardService: ProvideDashboardService(store, nil)},
store, store,
nil, nil,
) )

@ -1,4 +1,4 @@
package api package permissions
import ( import (
"context" "context"

@ -1,4 +1,4 @@
package api package permissions
import ( import (
"context" "context"
@ -7,14 +7,14 @@ import (
) )
type mockDatasourcePermissionService struct { type mockDatasourcePermissionService struct {
dsResult []*models.DataSource DsResult []*models.DataSource
} }
func (m *mockDatasourcePermissionService) FilterDatasourcesBasedOnQueryPermissions(ctx context.Context, cmd *models.DatasourcesPermissionFilterQuery) error { func (m *mockDatasourcePermissionService) FilterDatasourcesBasedOnQueryPermissions(ctx context.Context, cmd *models.DatasourcesPermissionFilterQuery) error {
cmd.Result = m.dsResult cmd.Result = m.DsResult
return nil return nil
} }
func newMockDatasourcePermissionService() *mockDatasourcePermissionService { func NewMockDatasourcePermissionService() *mockDatasourcePermissionService {
return &mockDatasourcePermissionService{} return &mockDatasourcePermissionService{}
} }

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager" dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
@ -195,7 +196,8 @@ func createDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, user models.Sign
} }
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
dashboard, err := dashboardservice.ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), dashItem, true) dashAlertExtractor := alerting.ProvideDashAlertExtractorService(nil, nil)
dashboard, err := dashboardservice.ProvideDashboardService(dashboardStore, dashAlertExtractor).SaveDashboard(context.Background(), dashItem, true)
require.NoError(t, err) require.NoError(t, err)
return dashboard return dashboard
@ -206,7 +208,7 @@ func createFolderWithACL(t *testing.T, sqlStore *sqlstore.SQLStore, title string
t.Helper() t.Helper()
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
d := dashboardservice.ProvideDashboardService(dashboardStore) d := dashboardservice.ProvideDashboardService(dashboardStore, nil)
s := dashboardservice.ProvideFolderService(d, dashboardStore, nil) s := dashboardservice.ProvideFolderService(d, dashboardStore, nil)
t.Logf("Creating folder with title and UID %q", title) t.Logf("Creating folder with title and UID %q", title)
folder, err := s.CreateFolder(context.Background(), &user, user.OrgId, title, title) folder, err := s.CreateFolder(context.Background(), &user, user.OrgId, title, title)
@ -292,7 +294,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
role := models.ROLE_ADMIN role := models.ROLE_ADMIN
sqlStore := sqlstore.InitTestDB(t) sqlStore := sqlstore.InitTestDB(t)
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
dashboardService := dashboardservice.ProvideDashboardService(dashboardStore) dashboardService := dashboardservice.ProvideDashboardService(dashboardStore, &alerting.DashAlertExtractorService{})
service := LibraryElementService{ service := LibraryElementService{
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
SQLStore: sqlStore, SQLStore: sqlStore,

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager" dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
@ -1416,7 +1417,8 @@ func createDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, user *models.Sig
} }
dashboadStore := database.ProvideDashboardStore(sqlStore) dashboadStore := database.ProvideDashboardStore(sqlStore)
dashboard, err := dashboardservice.ProvideDashboardService(dashboadStore).SaveDashboard(context.Background(), dashItem, true) dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil)
dashboard, err := dashboardservice.ProvideDashboardService(dashboadStore, dashAlertService).SaveDashboard(context.Background(), dashItem, true)
require.NoError(t, err) require.NoError(t, err)
return dashboard return dashboard
@ -1427,7 +1429,7 @@ func createFolderWithACL(t *testing.T, sqlStore *sqlstore.SQLStore, title string
t.Helper() t.Helper()
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
d := dashboardservice.ProvideDashboardService(dashboardStore) d := dashboardservice.ProvideDashboardService(dashboardStore, nil)
s := dashboardservice.ProvideFolderService(d, dashboardStore, nil) s := dashboardservice.ProvideFolderService(d, dashboardStore, nil)
t.Logf("Creating folder with title and UID %q", title) t.Logf("Creating folder with title and UID %q", title)
folder, err := s.CreateFolder(context.Background(), user, user.OrgId, title, title) folder, err := s.CreateFolder(context.Background(), user, user.OrgId, title, title)
@ -1516,7 +1518,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
role := models.ROLE_ADMIN role := models.ROLE_ADMIN
sqlStore := sqlstore.InitTestDB(t) sqlStore := sqlstore.InitTestDB(t)
dashboardStore := database.ProvideDashboardStore(sqlStore) dashboardStore := database.ProvideDashboardStore(sqlStore)
folderService := dashboardservice.ProvideFolderService(dashboardservice.ProvideDashboardService(dashboardStore), dashboardStore, nil) folderService := dashboardservice.ProvideFolderService(dashboardservice.ProvideDashboardService(dashboardStore, &alerting.DashAlertExtractorService{}), dashboardStore, nil)
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService) elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService)
service := LibraryPanelService{ service := LibraryPanelService{

@ -41,7 +41,7 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, *
sqlStore := sqlstore.InitTestDB(t) sqlStore := sqlstore.InitTestDB(t)
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore)) secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
dashboardStore := databasestore.ProvideDashboardStore(sqlStore) dashboardStore := databasestore.ProvideDashboardStore(sqlStore)
folderService := dashboardservice.ProvideFolderService(dashboardservice.ProvideDashboardService(dashboardStore), dashboardStore, nil) folderService := dashboardservice.ProvideFolderService(dashboardservice.ProvideDashboardService(dashboardStore, nil), dashboardStore, nil)
ng, err := ngalert.ProvideService( ng, err := ngalert.ProvideService(
cfg, nil, routing.NewRouteRegister(), sqlStore, cfg, nil, routing.NewRouteRegister(), sqlStore,
nil, nil, nil, nil, secretsService, nil, m, folderService, nil, nil, nil, nil, secretsService, nil, m, folderService,

@ -503,6 +503,7 @@ func (m *SQLStoreMock) GetDataSourcesByType(ctx context.Context, query *models.G
} }
func (m *SQLStoreMock) GetDefaultDataSource(ctx context.Context, query *models.GetDefaultDataSourceQuery) error { func (m *SQLStoreMock) GetDefaultDataSource(ctx context.Context, query *models.GetDefaultDataSourceQuery) error {
query.Result = m.ExpectedDatasource
return m.ExpectedError return m.ExpectedError
} }

Loading…
Cancel
Save