PluginManager: Make Plugins, Renderer and DataSources non-global (#31866)

* PluginManager: Make Plugins and DataSources non-global

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix integration tests

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Replace outdated command

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* DashboardService: Ensure it gets constructed with necessary parameters

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix build

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* DashboardService: Ensure it gets constructed with necessary parameters

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Remove dead code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix test

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix test

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Remove FocusConvey

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix test

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Remove dead code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Undo interface changes

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Rename to DataSourceCount

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Consolidate dashboard interfaces into one

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix tests

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix tests

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix test

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix tests

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix tests

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix tests

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix tests

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix dashboard integration tests

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Arve Knudsen
2021-03-17 16:06:10 +01:00
committed by GitHub
parent f1df32ac03
commit 87c3a2b790
95 changed files with 2455 additions and 2426 deletions
+10
View File
@@ -0,0 +1,10 @@
package api
import "github.com/grafana/grafana/pkg/models"
// updateDashboardACL updates a dashboard's ACL items.
//
// Stubbable by tests.
var updateDashboardACL = func(hs *HTTPServer, dashID int64, items []*models.DashboardAcl) error {
return hs.SQLStore.UpdateDashboardACL(dashID, items)
}
+5 -5
View File
@@ -266,7 +266,7 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Get("/datasources/id/:name", routing.Wrap(GetDataSourceIdByName), reqSignedIn) apiRoute.Get("/datasources/id/:name", routing.Wrap(GetDataSourceIdByName), reqSignedIn)
apiRoute.Get("/plugins", routing.Wrap(hs.GetPluginList)) apiRoute.Get("/plugins", routing.Wrap(hs.GetPluginList))
apiRoute.Get("/plugins/:pluginId/settings", routing.Wrap(GetPluginSettingByID)) apiRoute.Get("/plugins/:pluginId/settings", routing.Wrap(hs.GetPluginSettingByID))
apiRoute.Get("/plugins/:pluginId/markdown/:name", routing.Wrap(hs.GetPluginMarkdown)) apiRoute.Get("/plugins/:pluginId/markdown/:name", routing.Wrap(hs.GetPluginMarkdown))
apiRoute.Get("/plugins/:pluginId/health", routing.Wrap(hs.CheckHealth)) apiRoute.Get("/plugins/:pluginId/health", routing.Wrap(hs.CheckHealth))
apiRoute.Any("/plugins/:pluginId/resources", hs.CallResource) apiRoute.Any("/plugins/:pluginId/resources", hs.CallResource)
@@ -288,13 +288,13 @@ func (hs *HTTPServer) registerRoutes() {
// Folders // Folders
apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) { apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) {
folderRoute.Get("/", routing.Wrap(GetFolders)) folderRoute.Get("/", routing.Wrap(hs.GetFolders))
folderRoute.Get("/id/:id", routing.Wrap(GetFolderByID)) folderRoute.Get("/id/:id", routing.Wrap(hs.GetFolderByID))
folderRoute.Post("/", bind(models.CreateFolderCommand{}), routing.Wrap(hs.CreateFolder)) folderRoute.Post("/", bind(models.CreateFolderCommand{}), routing.Wrap(hs.CreateFolder))
folderRoute.Group("/:uid", func(folderUidRoute routing.RouteRegister) { folderRoute.Group("/:uid", func(folderUidRoute routing.RouteRegister) {
folderUidRoute.Get("/", routing.Wrap(GetFolderByUID)) folderUidRoute.Get("/", routing.Wrap(hs.GetFolderByUID))
folderUidRoute.Put("/", bind(models.UpdateFolderCommand{}), routing.Wrap(UpdateFolder)) folderUidRoute.Put("/", bind(models.UpdateFolderCommand{}), routing.Wrap(hs.UpdateFolder))
folderUidRoute.Delete("/", routing.Wrap(hs.DeleteFolder)) folderUidRoute.Delete("/", routing.Wrap(hs.DeleteFolder))
folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) { folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) {
+8 -9
View File
@@ -8,7 +8,6 @@ import (
"path/filepath" "path/filepath"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
@@ -122,7 +121,7 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
meta.FolderUrl = query.Result.GetUrl() meta.FolderUrl = query.Result.GetUrl()
} }
svc := dashboards.NewProvisioningService() svc := dashboards.NewProvisioningService(hs.SQLStore)
provisioningData, err := svc.GetProvisionedDashboardDataByDashboardID(dash.Id) provisioningData, err := svc.GetProvisionedDashboardDataByDashboardID(dash.Id)
if err != nil { if err != nil {
return response.Error(500, "Error while checking if dashboard is provisioned", err) return response.Error(500, "Error while checking if dashboard is provisioned", err)
@@ -227,7 +226,7 @@ func (hs *HTTPServer) deleteDashboard(c *models.ReqContext) response.Response {
} }
} }
svc := dashboards.NewService() svc := dashboards.NewService(hs.SQLStore)
err := svc.DeleteDashboard(dash.Id, c.OrgId) err := svc.DeleteDashboard(dash.Id, c.OrgId)
if err != nil { if err != nil {
var dashboardErr models.DashboardErr var dashboardErr models.DashboardErr
@@ -264,7 +263,7 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboa
} }
} }
svc := dashboards.NewProvisioningService() svc := dashboards.NewProvisioningService(hs.SQLStore)
provisioningData, err := svc.GetProvisionedDashboardDataByDashboardID(dash.Id) provisioningData, err := svc.GetProvisionedDashboardDataByDashboardID(dash.Id)
if err != nil { if err != nil {
return response.Error(500, "Error while checking if dashboard is provisioned", err) return response.Error(500, "Error while checking if dashboard is provisioned", err)
@@ -291,15 +290,15 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboa
Overwrite: cmd.Overwrite, Overwrite: cmd.Overwrite,
} }
dashSvc := dashboards.NewService() dashSvc := dashboards.NewService(hs.SQLStore)
dashboard, err := dashSvc.SaveDashboard(dashItem, allowUiUpdate) dashboard, err := dashSvc.SaveDashboard(dashItem, allowUiUpdate)
if err != nil { if err != nil {
return dashboardSaveErrorToApiResponse(err) return hs.dashboardSaveErrorToApiResponse(err)
} }
if hs.Cfg.EditorsCanAdmin && newDashboard { if hs.Cfg.EditorsCanAdmin && newDashboard {
inFolder := cmd.FolderId > 0 inFolder := cmd.FolderId > 0
err := dashboards.MakeUserAdmin(hs.Bus, cmd.OrgId, cmd.UserId, dashboard.Id, !inFolder) err := dashSvc.MakeUserAdmin(cmd.OrgId, cmd.UserId, dashboard.Id, !inFolder)
if err != nil { if err != nil {
hs.log.Error("Could not make user admin", "dashboard", dashboard.Title, "user", c.SignedInUser.UserId, "error", err) hs.log.Error("Could not make user admin", "dashboard", dashboard.Title, "user", c.SignedInUser.UserId, "error", err)
} }
@@ -335,7 +334,7 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboa
}) })
} }
func dashboardSaveErrorToApiResponse(err error) response.Response { func (hs *HTTPServer) dashboardSaveErrorToApiResponse(err error) response.Response {
var dashboardErr models.DashboardErr var dashboardErr models.DashboardErr
if ok := errors.As(err, &dashboardErr); ok { if ok := errors.As(err, &dashboardErr); ok {
if body := dashboardErr.Body(); body != nil { if body := dashboardErr.Body(); body != nil {
@@ -360,7 +359,7 @@ func dashboardSaveErrorToApiResponse(err error) response.Response {
if ok := errors.As(err, &pluginErr); ok { if ok := errors.As(err, &pluginErr); ok {
message := fmt.Sprintf("The dashboard belongs to plugin %s.", pluginErr.PluginId) message := fmt.Sprintf("The dashboard belongs to plugin %s.", pluginErr.PluginId)
// look up plugin name // look up plugin name
if pluginDef, exist := manager.Plugins[pluginErr.PluginId]; exist { if pluginDef := hs.PluginManager.GetPlugin(pluginErr.PluginId); pluginDef != nil {
message = fmt.Sprintf("The dashboard belongs to plugin %s.", pluginDef.Name) message = fmt.Sprintf("The dashboard belongs to plugin %s.", pluginDef.Name)
} }
return response.JSON(412, util.DynMap{"status": "plugin-dashboard", "message": message}) return response.JSON(412, util.DynMap{"status": "plugin-dashboard", "message": message})
+5 -8
View File
@@ -6,7 +6,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
) )
@@ -68,11 +67,9 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext, apiCmd dt
return dashboardGuardianResponse(err) return dashboardGuardianResponse(err)
} }
cmd := models.UpdateDashboardAclCommand{} var items []*models.DashboardAcl
cmd.DashboardID = dashID
for _, item := range apiCmd.Items { for _, item := range apiCmd.Items {
cmd.Items = append(cmd.Items, &models.DashboardAcl{ items = append(items, &models.DashboardAcl{
OrgID: c.OrgId, OrgID: c.OrgId,
DashboardID: dashID, DashboardID: dashID,
UserID: item.UserID, UserID: item.UserID,
@@ -88,9 +85,9 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext, apiCmd dt
if err != nil { if err != nil {
return response.Error(500, "Error while retrieving hidden permissions", err) return response.Error(500, "Error while retrieving hidden permissions", err)
} }
cmd.Items = append(cmd.Items, hiddenACL...) items = append(items, hiddenACL...)
if okToUpdate, err := g.CheckPermissionBeforeUpdate(models.PERMISSION_ADMIN, cmd.Items); err != nil || !okToUpdate { if okToUpdate, err := g.CheckPermissionBeforeUpdate(models.PERMISSION_ADMIN, items); err != nil || !okToUpdate {
if err != nil { if err != nil {
if errors.Is(err, guardian.ErrGuardianPermissionExists) || errors.Is(err, guardian.ErrGuardianOverride) { if errors.Is(err, guardian.ErrGuardianPermissionExists) || errors.Is(err, guardian.ErrGuardianOverride) {
return response.Error(400, err.Error(), err) return response.Error(400, err.Error(), err)
@@ -102,7 +99,7 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext, apiCmd dt
return response.Error(403, "Cannot remove own admin permission for a folder", nil) return response.Error(403, "Cannot remove own admin permission for a folder", nil)
} }
if err := bus.Dispatch(&cmd); err != nil { if err := updateDashboardACL(hs, dashID, items); err != nil {
if errors.Is(err, models.ErrDashboardAclInfoMissing) || if errors.Is(err, models.ErrDashboardAclInfoMissing) ||
errors.Is(err, models.ErrDashboardPermissionDashboardEmpty) { errors.Is(err, models.ErrDashboardPermissionDashboardEmpty) {
return response.Error(409, err.Error(), err) return response.Error(409, err.Error(), err)
+25 -12
View File
@@ -48,7 +48,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
setUp() setUp()
callUpdateDashboardPermissions(sc) callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 404, sc.resp.Code) assert.Equal(t, 404, sc.resp.Code)
}, },
}, hs) }, hs)
@@ -91,7 +91,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
setUp() setUp()
callUpdateDashboardPermissions(sc) callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
}, },
}, hs) }, hs)
@@ -151,7 +151,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
setUp() setUp()
callUpdateDashboardPermissions(sc) callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
}, },
}, hs) }, hs)
@@ -189,7 +189,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
setUp() setUp()
callUpdateDashboardPermissions(sc) callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code) assert.Equal(t, 400, sc.resp.Code)
}, },
}, hs) }, hs)
@@ -217,7 +217,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/dashboards/id/:id/permissions", routePattern: "/api/dashboards/id/:id/permissions",
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
callUpdateDashboardPermissions(sc) callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code) assert.Equal(t, 400, sc.resp.Code)
respJSON, err := jsonMap(sc.resp.Body.Bytes()) respJSON, err := jsonMap(sc.resp.Body.Bytes())
require.NoError(t, err) require.NoError(t, err)
@@ -260,7 +260,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
setUp() setUp()
callUpdateDashboardPermissions(sc) callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code) assert.Equal(t, 400, sc.resp.Code)
}, },
}, hs) }, hs)
@@ -335,13 +335,20 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
setUp() setUp()
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error { // TODO: Replace this fake with a fake SQLStore instead (once we can use an interface in its stead)
assert.Len(t, cmd.Items, 4) origUpdateDashboardACL := updateDashboardACL
return nil t.Cleanup(func() {
updateDashboardACL = origUpdateDashboardACL
}) })
var gotItems []*models.DashboardAcl
updateDashboardACL = func(hs *HTTPServer, folderID int64, items []*models.DashboardAcl) error {
gotItems = items
return nil
}
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
assert.Len(t, gotItems, 4)
}, },
}, hs) }, hs)
}) })
@@ -353,10 +360,16 @@ func callGetDashboardPermissions(sc *scenarioContext, hs *HTTPServer) {
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
} }
func callUpdateDashboardPermissions(sc *scenarioContext) { func callUpdateDashboardPermissions(t *testing.T, sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error { t.Helper()
return nil
origUpdateDashboardACL := updateDashboardACL
t.Cleanup(func() {
updateDashboardACL = origUpdateDashboardACL
}) })
updateDashboardACL = func(hs *HTTPServer, dashID int64, items []*models.DashboardAcl) error {
return nil
}
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
} }
+52 -61
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
dboards "github.com/grafana/grafana/pkg/dashboards"
"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/alerting"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
@@ -28,7 +29,10 @@ func TestGetHomeDashboard(t *testing.T) {
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.StaticRootPath = "../../public/" cfg.StaticRootPath = "../../public/"
hs := &HTTPServer{Cfg: cfg, Bus: bus.New()} hs := &HTTPServer{
Cfg: cfg, Bus: bus.New(),
PluginManager: &fakePluginManager{},
}
hs.Bus.AddHandler(func(query *models.GetPreferencesWithDefaultsQuery) error { hs.Bus.AddHandler(func(query *models.GetPreferencesWithDefaultsQuery) error {
query.Result = &models.Preferences{ query.Result = &models.Preferences{
HomeDashboardId: 0, HomeDashboardId: 0,
@@ -102,11 +106,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
return nil return nil
}) })
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
viewerRole := models.ROLE_VIEWER viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR editorRole := models.ROLE_EDITOR
@@ -165,7 +164,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/db/:slug", role, func(sc *scenarioContext) { "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUp() state := setUp()
CallDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()}) callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug) assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
@@ -175,7 +174,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUp() state := setUp()
CallDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()}) callDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
@@ -230,7 +229,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/db/:slug", role, func(sc *scenarioContext) { "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUp() state := setUp()
CallDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()}) callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug) assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
}) })
@@ -239,7 +238,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUp() state := setUp()
CallDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()}) callDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
}) })
@@ -281,11 +280,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
}) })
setting.ViewersCanEdit = false setting.ViewersCanEdit = false
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
bus.AddHandler("test", func(query *models.GetDashboardsBySlugQuery) error { bus.AddHandler("test", func(query *models.GetDashboardsBySlugQuery) error {
dashboards := []*models.Dashboard{fakeDash} dashboards := []*models.Dashboard{fakeDash}
query.Result = dashboards query.Result = dashboards
@@ -354,7 +348,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/db/:slug", role, func(sc *scenarioContext) { "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUp() state := setUp()
CallDeleteDashboardBySlug(sc, hs) callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug) assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
}) })
@@ -363,7 +357,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUp() state := setUp()
CallDeleteDashboardByUID(sc, hs) callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
}) })
@@ -414,7 +408,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/db/:slug", role, func(sc *scenarioContext) { "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUp() state := setUp()
CallDeleteDashboardBySlug(sc, hs) callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug) assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
}) })
@@ -423,7 +417,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUp() state := setUp()
CallDeleteDashboardByUID(sc, hs) callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
}) })
@@ -492,7 +486,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUpInner() state := setUpInner()
CallDeleteDashboardBySlug(sc, hs) callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug) assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
}) })
@@ -500,7 +494,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUpInner() state := setUpInner()
CallDeleteDashboardByUID(sc, hs) callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
}) })
@@ -570,7 +564,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUpInner() state := setUpInner()
CallDeleteDashboardBySlug(sc, hs) callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug) assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
}) })
@@ -578,7 +572,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUpInner() state := setUpInner()
CallDeleteDashboardByUID(sc, hs) callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
}) })
@@ -624,7 +618,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUpInner() state := setUpInner()
CallDeleteDashboardBySlug(sc, hs) callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug) assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
}) })
@@ -632,7 +626,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUpInner() state := setUpInner()
CallDeleteDashboardByUID(sc, hs) callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
}) })
@@ -690,7 +684,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/child-dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUpInner() state := setUpInner()
CallDeleteDashboardBySlug(sc, hs) callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug) assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
}) })
@@ -698,7 +692,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUpInner() state := setUpInner()
CallDeleteDashboardByUID(sc, hs) callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
}) })
@@ -730,11 +724,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
dashTwo.FolderId = 3 dashTwo.FolderId = 3
dashTwo.HasAcl = false dashTwo.HasAcl = false
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
bus.AddHandler("test", func(query *models.GetDashboardsBySlugQuery) error { bus.AddHandler("test", func(query *models.GetDashboardsBySlugQuery) error {
dashboards := []*models.Dashboard{dashOne, dashTwo} dashboards := []*models.Dashboard{dashOne, dashTwo}
query.Result = dashboards query.Result = dashboards
@@ -743,14 +732,15 @@ func TestDashboardAPIEndpoint(t *testing.T) {
role := models.ROLE_EDITOR role := models.ROLE_EDITOR
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/dash",
CallDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()}) "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 412, sc.resp.Code) assert.Equal(t, 412, sc.resp.Code)
result := sc.ToJSON() result := sc.ToJSON()
assert.Equal(t, "multiple-slugs-exists", result.Get("status").MustString()) assert.Equal(t, "multiple-slugs-exists", result.Get("status").MustString())
assert.Equal(t, models.ErrDashboardsWithSameSlugExists.Error(), result.Get("message").MustString()) assert.Equal(t, models.ErrDashboardsWithSameSlugExists.Error(), result.Get("message").MustString())
}) })
}) })
t.Run("Post dashboard response tests", func(t *testing.T) { t.Run("Post dashboard response tests", func(t *testing.T) {
@@ -856,11 +846,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
return nil return nil
}) })
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
bus.AddHandler("test", func(query *models.GetDashboardVersionQuery) error { bus.AddHandler("test", func(query *models.GetDashboardVersionQuery) error {
query.Result = &models.DashboardVersion{ query.Result = &models.DashboardVersion{
Data: simplejson.NewFromAny(map[string]interface{}{ Data: simplejson.NewFromAny(map[string]interface{}{
@@ -1017,10 +1002,13 @@ func TestDashboardAPIEndpoint(t *testing.T) {
return nil return nil
}) })
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error { origGetProvisionedData := dashboards.GetProvisionedData
query.Result = &models.DashboardProvisioning{ExternalId: "/tmp/grafana/dashboards/test/dashboard1.json"} t.Cleanup(func() {
return nil dashboards.GetProvisionedData = origGetProvisionedData
}) })
dashboards.GetProvisionedData = func(dboards.Store, int64) (*models.DashboardProvisioning, error) {
return &models.DashboardProvisioning{ExternalId: "/tmp/grafana/dashboards/test/dashboard1.json"}, nil
}
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error { bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
query.Result = []*models.DashboardAclInfoDTO{ query.Result = []*models.DashboardAclInfoDTO{
@@ -1030,20 +1018,21 @@ func TestDashboardAPIEndpoint(t *testing.T) {
}) })
} }
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/dash", "/api/dashboards/db/:slug", models.ROLE_EDITOR, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/dash",
setUp() "/api/dashboards/db/:slug", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
CallDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()}) callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 400, sc.resp.Code) assert.Equal(t, 400, sc.resp.Code)
result := sc.ToJSON() result := sc.ToJSON()
assert.Equal(t, models.ErrDashboardCannotDeleteProvisionedDashboard.Error(), result.Get("error").MustString()) assert.Equal(t, models.ErrDashboardCannotDeleteProvisionedDashboard.Error(), result.Get("error").MustString())
}) })
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/abcdefghi", "/api/dashboards/db/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/abcdefghi", "/api/dashboards/db/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp() setUp()
CallDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()}) callDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 400, sc.resp.Code) assert.Equal(t, 400, sc.resp.Code)
result := sc.ToJSON() result := sc.ToJSON()
@@ -1141,7 +1130,7 @@ func callGetDashboardVersions(sc *scenarioContext) {
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
} }
func CallDeleteDashboardBySlug(sc *scenarioContext, hs *HTTPServer) { func callDeleteDashboardBySlug(sc *scenarioContext, hs *HTTPServer) {
bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error { bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error {
return nil return nil
}) })
@@ -1150,7 +1139,7 @@ func CallDeleteDashboardBySlug(sc *scenarioContext, hs *HTTPServer) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
} }
func CallDeleteDashboardByUID(sc *scenarioContext, hs *HTTPServer) { func callDeleteDashboardByUID(sc *scenarioContext, hs *HTTPServer) {
bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error { bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error {
return nil return nil
}) })
@@ -1187,6 +1176,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
QuotaService: &quota.QuotaService{ QuotaService: &quota.QuotaService{
Cfg: cfg, Cfg: cfg,
}, },
PluginManager: &fakePluginManager{},
} }
sc := setupScenarioContext(t, url) sc := setupScenarioContext(t, url)
@@ -1204,7 +1194,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
dashboards.NewProvisioningService = origProvisioningService dashboards.NewProvisioningService = origProvisioningService
}) })
dashboards.MockDashboardService(mock) dashboards.MockDashboardService(mock)
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService { dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
return mockDashboardProvisioningService{} return mockDashboardProvisioningService{}
} }
@@ -1268,7 +1258,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
dashboards.NewService = origNewDashboardService dashboards.NewService = origNewDashboardService
dashboards.NewProvisioningService = origProvisioningService dashboards.NewProvisioningService = origProvisioningService
}) })
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService { dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
return mockDashboardProvisioningService{} return mockDashboardProvisioningService{}
} }
dashboards.MockDashboardService(mock) dashboards.MockDashboardService(mock)
@@ -1290,6 +1280,7 @@ type mockDashboardProvisioningService struct {
dashboards.DashboardProvisioningService dashboards.DashboardProvisioningService
} }
func (m mockDashboardProvisioningService) GetProvisionedDashboardDataByDashboardID(dashboardId int64) (*models.DashboardProvisioning, error) { func (s mockDashboardProvisioningService) GetProvisionedDashboardDataByDashboardID(dashboardID int64) (
return &models.DashboardProvisioning{}, nil *models.DashboardProvisioning, error) {
return nil, nil
} }
+2 -3
View File
@@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/api/pluginproxy" "github.com/grafana/grafana/pkg/api/pluginproxy"
"github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/manager"
) )
// ProxyDataSourceRequest proxies datasource requests // ProxyDataSourceRequest proxies datasource requests
@@ -35,8 +34,8 @@ func (hs *HTTPServer) ProxyDataSourceRequest(c *models.ReqContext) {
} }
// find plugin // find plugin
plugin, ok := manager.DataSources[ds.Type] plugin := hs.PluginManager.GetDataSource(ds.Type)
if !ok { if plugin == nil {
c.JsonApiErr(http.StatusInternalServerError, "Unable to find datasource plugin", err) c.JsonApiErr(http.StatusInternalServerError, "Unable to find datasource plugin", err)
return return
} }
+5 -6
View File
@@ -14,7 +14,6 @@ import (
"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/plugins/adapters" "github.com/grafana/grafana/pkg/plugins/adapters"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
@@ -47,7 +46,7 @@ func (hs *HTTPServer) GetDataSources(c *models.ReqContext) response.Response {
ReadOnly: ds.ReadOnly, ReadOnly: ds.ReadOnly,
} }
if plugin, exists := manager.DataSources[ds.Type]; exists { if plugin := hs.PluginManager.GetDataSource(ds.Type); plugin != nil {
dsItem.TypeLogoUrl = plugin.Info.Logos.Small dsItem.TypeLogoUrl = plugin.Info.Logos.Small
dsItem.TypeName = plugin.Name dsItem.TypeName = plugin.Name
} else { } else {
@@ -363,8 +362,8 @@ func (hs *HTTPServer) CallDatasourceResource(c *models.ReqContext) {
} }
// find plugin // find plugin
plugin, ok := manager.DataSources[ds.Type] plugin := hs.PluginManager.GetDataSource(ds.Type)
if !ok { if plugin == nil {
c.JsonApiErr(500, "Unable to find datasource plugin", err) c.JsonApiErr(500, "Unable to find datasource plugin", err)
return return
} }
@@ -428,8 +427,8 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo
return response.Error(500, "Unable to load datasource metadata", err) return response.Error(500, "Unable to load datasource metadata", err)
} }
plugin, ok := hs.PluginManager.GetDatasource(ds.Type) plugin := hs.PluginManager.GetDataSource(ds.Type)
if !ok { if plugin == nil {
return response.Error(500, "Unable to find datasource plugin", err) return response.Error(500, "Unable to find datasource plugin", err)
} }
+5 -1
View File
@@ -35,7 +35,11 @@ func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
}) })
// handler func being tested // handler func being tested
hs := &HTTPServer{Bus: bus.GetBus(), Cfg: setting.NewCfg()} hs := &HTTPServer{
Bus: bus.GetBus(),
Cfg: setting.NewCfg(),
PluginManager: &fakePluginManager{},
}
sc.handlerFunc = hs.GetDataSources sc.handlerFunc = hs.GetDataSources
sc.fakeReq("GET", "/api/datasources").exec() sc.fakeReq("GET", "/api/datasources").exec()
+1 -2
View File
@@ -3,7 +3,6 @@ package dtos
import ( import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
) )
type PluginSetting struct { type PluginSetting struct {
@@ -64,6 +63,6 @@ type ImportDashboardCommand struct {
Path string `json:"path"` Path string `json:"path"`
Overwrite bool `json:"overwrite"` Overwrite bool `json:"overwrite"`
Dashboard *simplejson.Json `json:"dashboard"` Dashboard *simplejson.Json `json:"dashboard"`
Inputs []manager.ImportDashboardInput `json:"inputs"` Inputs []plugins.ImportDashboardInput `json:"inputs"`
FolderId int64 `json:"folderId"` FolderId int64 `json:"folderId"`
} }
+19
View File
@@ -0,0 +1,19 @@
package api
import "github.com/grafana/grafana/pkg/plugins"
type fakePluginManager struct {
plugins.Manager
}
func (pm *fakePluginManager) GetPlugin(id string) *plugins.PluginBase {
return nil
}
func (pm *fakePluginManager) GetDataSource(id string) *plugins.DataSourcePlugin {
return nil
}
func (pm *fakePluginManager) Renderer() *plugins.RendererPlugin {
return nil
}
+16 -16
View File
@@ -14,8 +14,8 @@ import (
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
func GetFolders(c *models.ReqContext) response.Response { func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folders, err := s.GetFolders(c.QueryInt64("limit")) folders, err := s.GetFolders(c.QueryInt64("limit"))
if err != nil { if err != nil {
@@ -35,10 +35,9 @@ func GetFolders(c *models.ReqContext) response.Response {
return response.JSON(200, result) return response.JSON(200, result)
} }
func GetFolderByUID(c *models.ReqContext) response.Response { func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.GetFolderByUID(c.Params(":uid")) folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil { if err != nil {
return toFolderError(err) return toFolderError(err)
} }
@@ -47,8 +46,8 @@ func GetFolderByUID(c *models.ReqContext) response.Response {
return response.JSON(200, toFolderDto(g, folder)) return response.JSON(200, toFolderDto(g, folder))
} }
func GetFolderByID(c *models.ReqContext) response.Response { func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.GetFolderByID(c.ParamsInt64(":id")) folder, err := s.GetFolderByID(c.ParamsInt64(":id"))
if err != nil { if err != nil {
return toFolderError(err) return toFolderError(err)
@@ -59,24 +58,25 @@ func GetFolderByID(c *models.ReqContext) response.Response {
} }
func (hs *HTTPServer) CreateFolder(c *models.ReqContext, cmd models.CreateFolderCommand) response.Response { func (hs *HTTPServer) CreateFolder(c *models.ReqContext, cmd models.CreateFolderCommand) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
err := s.CreateFolder(&cmd) folder, err := s.CreateFolder(cmd.Title, cmd.Uid)
if err != nil { if err != nil {
return toFolderError(err) return toFolderError(err)
} }
if hs.Cfg.EditorsCanAdmin { if hs.Cfg.EditorsCanAdmin {
if err := dashboards.MakeUserAdmin(hs.Bus, c.OrgId, c.SignedInUser.UserId, cmd.Result.Id, true); err != nil { if err := s.MakeUserAdmin(c.OrgId, c.SignedInUser.UserId, folder.Id, true); err != nil {
hs.log.Error("Could not make user admin", "folder", cmd.Result.Title, "user", c.SignedInUser.UserId, "error", err) hs.log.Error("Could not make user admin", "folder", folder.Title, "user",
c.SignedInUser.UserId, "error", err)
} }
} }
g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser) g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
return response.JSON(200, toFolderDto(g, cmd.Result)) return response.JSON(200, toFolderDto(g, folder))
} }
func UpdateFolder(c *models.ReqContext, cmd models.UpdateFolderCommand) response.Response { func (hs *HTTPServer) UpdateFolder(c *models.ReqContext, cmd models.UpdateFolderCommand) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
err := s.UpdateFolder(c.Params(":uid"), &cmd) err := s.UpdateFolder(c.Params(":uid"), &cmd)
if err != nil { if err != nil {
return toFolderError(err) return toFolderError(err)
@@ -87,7 +87,7 @@ func UpdateFolder(c *models.ReqContext, cmd models.UpdateFolderCommand) response
} }
func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { // temporarily adding this function to HTTPServer, will be removed from HTTPServer when librarypanels featuretoggle is removed func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { // temporarily adding this function to HTTPServer, will be removed from HTTPServer when librarypanels featuretoggle is removed
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
if hs.Cfg.IsPanelLibraryEnabled() { if hs.Cfg.IsPanelLibraryEnabled() {
err := hs.LibraryPanelService.DeleteLibraryPanelsInFolder(c, c.Params(":uid")) err := hs.LibraryPanelService.DeleteLibraryPanelsInFolder(c, c.Params(":uid"))
if err != nil { if err != nil {
+7 -11
View File
@@ -6,7 +6,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
@@ -14,7 +13,7 @@ import (
) )
func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Response { func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.GetFolderByUID(c.Params(":uid")) folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil { if err != nil {
@@ -62,9 +61,8 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.
return response.Error(400, err.Error(), err) return response.Error(400, err.Error(), err)
} }
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser) s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.GetFolderByUID(c.Params(":uid")) folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil { if err != nil {
return toFolderError(err) return toFolderError(err)
} }
@@ -79,11 +77,9 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.
return toFolderError(models.ErrFolderAccessDenied) return toFolderError(models.ErrFolderAccessDenied)
} }
cmd := models.UpdateDashboardAclCommand{} var items []*models.DashboardAcl
cmd.DashboardID = folder.Id
for _, item := range apiCmd.Items { for _, item := range apiCmd.Items {
cmd.Items = append(cmd.Items, &models.DashboardAcl{ items = append(items, &models.DashboardAcl{
OrgID: c.OrgId, OrgID: c.OrgId,
DashboardID: folder.Id, DashboardID: folder.Id,
UserID: item.UserID, UserID: item.UserID,
@@ -99,9 +95,9 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.
if err != nil { if err != nil {
return response.Error(500, "Error while retrieving hidden permissions", err) return response.Error(500, "Error while retrieving hidden permissions", err)
} }
cmd.Items = append(cmd.Items, hiddenACL...) items = append(items, hiddenACL...)
if okToUpdate, err := g.CheckPermissionBeforeUpdate(models.PERMISSION_ADMIN, cmd.Items); err != nil || !okToUpdate { if okToUpdate, err := g.CheckPermissionBeforeUpdate(models.PERMISSION_ADMIN, items); err != nil || !okToUpdate {
if err != nil { if err != nil {
if errors.Is(err, guardian.ErrGuardianPermissionExists) || if errors.Is(err, guardian.ErrGuardianPermissionExists) ||
errors.Is(err, guardian.ErrGuardianOverride) { errors.Is(err, guardian.ErrGuardianOverride) {
@@ -114,7 +110,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.
return response.Error(403, "Cannot remove own admin permission for a folder", nil) return response.Error(403, "Cannot remove own admin permission for a folder", nil)
} }
if err := bus.Dispatch(&cmd); err != nil { if err := updateDashboardACL(hs, folder.Id, items); err != nil {
if errors.Is(err, models.ErrDashboardAclInfoMissing) { if errors.Is(err, models.ErrDashboardAclInfoMissing) {
err = models.ErrFolderAclInfoMissing err = models.ErrFolderAclInfoMissing
} }
+24 -12
View File
@@ -49,7 +49,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions", routePattern: "/api/folders/:uid/permissions",
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc) callUpdateFolderPermissions(t, sc)
assert.Equal(t, 404, sc.resp.Code) assert.Equal(t, 404, sc.resp.Code)
}, },
}, hs) }, hs)
@@ -92,7 +92,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions", routePattern: "/api/folders/:uid/permissions",
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc) callUpdateFolderPermissions(t, sc)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
}, },
}, hs) }, hs)
@@ -153,7 +153,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions", routePattern: "/api/folders/:uid/permissions",
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc) callUpdateFolderPermissions(t, sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
var resp struct { var resp struct {
@@ -205,7 +205,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions", routePattern: "/api/folders/:uid/permissions",
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc) callUpdateFolderPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code) assert.Equal(t, 400, sc.resp.Code)
}, },
}, hs) }, hs)
@@ -233,7 +233,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions", routePattern: "/api/folders/:uid/permissions",
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc) callUpdateFolderPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code) assert.Equal(t, 400, sc.resp.Code)
respJSON, err := jsonMap(sc.resp.Body.Bytes()) respJSON, err := jsonMap(sc.resp.Body.Bytes())
require.NoError(t, err) require.NoError(t, err)
@@ -279,7 +279,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions", routePattern: "/api/folders/:uid/permissions",
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc) callUpdateFolderPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code) assert.Equal(t, 400, sc.resp.Code)
}, },
}, hs) }, hs)
@@ -355,13 +355,19 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions", routePattern: "/api/folders/:uid/permissions",
cmd: cmd, cmd: cmd,
fn: func(sc *scenarioContext) { fn: func(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error { origUpdateDashboardACL := updateDashboardACL
assert.Len(t, cmd.Items, 4) t.Cleanup(func() {
return nil updateDashboardACL = origUpdateDashboardACL
}) })
var gotItems []*models.DashboardAcl
updateDashboardACL = func(hs *HTTPServer, dashID int64, items []*models.DashboardAcl) error {
gotItems = items
return nil
}
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
assert.Len(t, gotItems, 4)
}, },
}, hs) }, hs)
}) })
@@ -372,10 +378,16 @@ func callGetFolderPermissions(sc *scenarioContext, hs *HTTPServer) {
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
} }
func callUpdateFolderPermissions(sc *scenarioContext) { func callUpdateFolderPermissions(t *testing.T, sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error { t.Helper()
return nil
origUpdateDashboardACL := updateDashboardACL
t.Cleanup(func() {
updateDashboardACL = origUpdateDashboardACL
}) })
updateDashboardACL = func(hs *HTTPServer, dashID int64, items []*models.DashboardAcl) error {
return nil
}
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
} }
+12 -5
View File
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@@ -174,12 +175,16 @@ func updateFolderScenario(t *testing.T, desc string, url string, routePattern st
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
hs := HTTPServer{
Cfg: setting.NewCfg(),
}
sc := setupScenarioContext(t, url) sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
sc.context = c sc.context = c
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID} sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
return UpdateFolder(c, cmd) return hs.UpdateFolder(c, cmd)
}) })
origNewFolderService := dashboards.NewFolderService origNewFolderService := dashboards.NewFolderService
@@ -195,6 +200,8 @@ func updateFolderScenario(t *testing.T, desc string, url string, routePattern st
} }
type fakeFolderService struct { type fakeFolderService struct {
dashboards.FolderService
GetFoldersResult []*models.Folder GetFoldersResult []*models.Folder
GetFoldersError error GetFoldersError error
GetFolderByUIDResult *models.Folder GetFolderByUIDResult *models.Folder
@@ -222,9 +229,8 @@ func (s *fakeFolderService) GetFolderByUID(uid string) (*models.Folder, error) {
return s.GetFolderByUIDResult, s.GetFolderByUIDError return s.GetFolderByUIDResult, s.GetFolderByUIDError
} }
func (s *fakeFolderService) CreateFolder(cmd *models.CreateFolderCommand) error { func (s *fakeFolderService) CreateFolder(title, uid string) (*models.Folder, error) {
cmd.Result = s.CreateFolderResult return s.CreateFolderResult, s.CreateFolderError
return s.CreateFolderError
} }
func (s *fakeFolderService) UpdateFolder(existingUID string, cmd *models.UpdateFolderCommand) error { func (s *fakeFolderService) UpdateFolder(existingUID string, cmd *models.UpdateFolderCommand) error {
@@ -238,7 +244,8 @@ func (s *fakeFolderService) DeleteFolder(uid string) (*models.Folder, error) {
} }
func mockFolderService(mock *fakeFolderService) { func mockFolderService(mock *fakeFolderService) {
dashboards.NewFolderService = func(orgId int64, user *models.SignedInUser) dashboards.FolderService { dashboards.NewFolderService = func(orgId int64, user *models.SignedInUser,
dashboardStore dboards.Store) dashboards.FolderService {
return mock return mock
} }
} }
+1 -1
View File
@@ -43,10 +43,10 @@ func ReadSourceMapFromFS(dir string, path string) ([]byte, error) {
} }
type SourceMapStore struct { type SourceMapStore struct {
sync.Mutex
cache map[string]*sourceMap cache map[string]*sourceMap
cfg *setting.Cfg cfg *setting.Cfg
readSourceMap ReadSourceMapFn readSourceMap ReadSourceMapFn
sync.Mutex
} }
func NewSourceMapStore(cfg *setting.Cfg, readSourceMap ReadSourceMapFn) *SourceMapStore { func NewSourceMapStore(cfg *setting.Cfg, readSourceMap ReadSourceMapFn) *SourceMapStore {
+4 -5
View File
@@ -5,7 +5,6 @@ import (
"strconv" "strconv"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@@ -110,12 +109,12 @@ func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins *plu
// add data sources that are built in (meaning they are not added via data sources page, nor have any entry in // add data sources that are built in (meaning they are not added via data sources page, nor have any entry in
// the datasource table) // the datasource table)
for _, ds := range manager.DataSources { for _, ds := range hs.PluginManager.DataSources() {
if ds.BuiltIn { if ds.BuiltIn {
dataSources[ds.Name] = map[string]interface{}{ dataSources[ds.Name] = map[string]interface{}{
"type": ds.Type, "type": ds.Type,
"name": ds.Name, "name": ds.Name,
"meta": manager.DataSources[ds.Id], "meta": hs.PluginManager.GetDataSource(ds.Id),
} }
} }
} }
@@ -226,8 +225,8 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
"commit": commit, "commit": commit,
"buildstamp": buildstamp, "buildstamp": buildstamp,
"edition": hs.License.Edition(), "edition": hs.License.Edition(),
"latestVersion": hs.PluginManager.GrafanaLatestVersion, "latestVersion": hs.PluginManager.GrafanaLatestVersion(),
"hasUpdate": hs.PluginManager.GrafanaHasUpdate, "hasUpdate": hs.PluginManager.GrafanaHasUpdate(),
"env": setting.Env, "env": setting.Env,
"isEnterprise": hs.License.HasValidLicense(), "isEnterprise": hs.License.HasValidLicense(),
}, },
+8 -5
View File
@@ -39,18 +39,21 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg) (*macaron.Macaron, *HT
}) })
} }
bus.ClearBusHandlers() sqlStore := sqlstore.InitTestDB(t)
bus.AddHandler("sql", sqlstore.GetPluginSettings) pm := &manager.PluginManager{Cfg: cfg, SQLStore: sqlStore}
t.Cleanup(bus.ClearBusHandlers)
r := &rendering.RenderingService{Cfg: cfg} r := &rendering.RenderingService{
Cfg: cfg,
PluginManager: pm,
}
hs := &HTTPServer{ hs := &HTTPServer{
Cfg: cfg, Cfg: cfg,
Bus: bus.GetBus(), Bus: bus.GetBus(),
License: &licensing.OSSLicensingService{Cfg: cfg}, License: &licensing.OSSLicensingService{Cfg: cfg},
RenderService: r, RenderService: r,
PluginManager: &manager.PluginManager{Cfg: cfg}, SQLStore: sqlStore,
PluginManager: pm,
} }
m := macaron.New() m := macaron.New()
+2 -1
View File
@@ -13,6 +13,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/live" "github.com/grafana/grafana/pkg/services/live"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/search"
@@ -79,7 +80,7 @@ type HTTPServer struct {
License models.Licensing `inject:""` License models.Licensing `inject:""`
BackendPluginManager backendplugin.Manager `inject:""` BackendPluginManager backendplugin.Manager `inject:""`
PluginRequestValidator models.PluginRequestValidator `inject:""` PluginRequestValidator models.PluginRequestValidator `inject:""`
PluginManager *manager.PluginManager `inject:""` PluginManager plugins.Manager `inject:""`
SearchService *search.SearchService `inject:""` SearchService *search.SearchService `inject:""`
ShortURLService *shorturls.ShortURLService `inject:""` ShortURLService *shorturls.ShortURLService `inject:""`
Live *live.GrafanaLive `inject:""` Live *live.GrafanaLive `inject:""`
+2 -2
View File
@@ -400,8 +400,8 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
GoogleTagManagerId: setting.GoogleTagManagerId, GoogleTagManagerId: setting.GoogleTagManagerId,
BuildVersion: setting.BuildVersion, BuildVersion: setting.BuildVersion,
BuildCommit: setting.BuildCommit, BuildCommit: setting.BuildCommit,
NewGrafanaVersion: hs.PluginManager.GrafanaLatestVersion, NewGrafanaVersion: hs.PluginManager.GrafanaLatestVersion(),
NewGrafanaVersionExists: hs.PluginManager.GrafanaHasUpdate, NewGrafanaVersionExists: hs.PluginManager.GrafanaHasUpdate(),
AppName: setting.ApplicationName, AppName: setting.ApplicationName,
AppNameBodyClass: getAppNameBodyClass(hs.License.HasValidLicense()), AppNameBodyClass: getAppNameBodyClass(hs.License.HasValidLicense()),
FavIcon: "public/img/fav32.png", FavIcon: "public/img/fav32.png",
+10 -11
View File
@@ -25,8 +25,8 @@ var ErrPluginNotFound error = errors.New("plugin not found, no installed plugin
func (hs *HTTPServer) getPluginContext(pluginID string, user *models.SignedInUser) (backend.PluginContext, error) { func (hs *HTTPServer) getPluginContext(pluginID string, user *models.SignedInUser) (backend.PluginContext, error) {
pc := backend.PluginContext{} pc := backend.PluginContext{}
plugin, exists := manager.Plugins[pluginID] plugin := hs.PluginManager.GetPlugin(pluginID)
if !exists { if plugin == nil {
return pc, ErrPluginNotFound return pc, ErrPluginNotFound
} }
@@ -74,13 +74,12 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
} }
pluginSettingsMap, err := hs.PluginManager.GetPluginSettings(c.OrgId) pluginSettingsMap, err := hs.PluginManager.GetPluginSettings(c.OrgId)
if err != nil { if err != nil {
return response.Error(500, "Failed to get list of plugins", err) return response.Error(500, "Failed to get list of plugins", err)
} }
result := make(dtos.PluginList, 0) result := make(dtos.PluginList, 0)
for _, pluginDef := range manager.Plugins { for _, pluginDef := range hs.PluginManager.Plugins() {
// filter out app sub plugins // filter out app sub plugins
if embeddedFilter == "0" && pluginDef.IncludedInAppId != "" { if embeddedFilter == "0" && pluginDef.IncludedInAppId != "" {
continue continue
@@ -130,7 +129,7 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
} }
// filter out built in data sources // filter out built in data sources
if ds, exists := manager.DataSources[pluginDef.Id]; exists { if ds := hs.PluginManager.GetDataSource(pluginDef.Id); ds != nil {
if ds.BuiltIn { if ds.BuiltIn {
continue continue
} }
@@ -143,11 +142,11 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
return response.JSON(200, result) return response.JSON(200, result)
} }
func GetPluginSettingByID(c *models.ReqContext) response.Response { func (hs *HTTPServer) GetPluginSettingByID(c *models.ReqContext) response.Response {
pluginID := c.Params(":pluginId") pluginID := c.Params(":pluginId")
def, exists := manager.Plugins[pluginID] def := hs.PluginManager.GetPlugin(pluginID)
if !exists { if def == nil {
return response.Error(404, "Plugin not found, no installed plugin with that id", nil) return response.Error(404, "Plugin not found, no installed plugin with that id", nil)
} }
@@ -256,7 +255,7 @@ func (hs *HTTPServer) ImportDashboard(c *models.ReqContext, apiCmd dtos.ImportDa
dashInfo, err := hs.PluginManager.ImportDashboard(apiCmd.PluginId, apiCmd.Path, c.OrgId, apiCmd.FolderId, dashInfo, err := hs.PluginManager.ImportDashboard(apiCmd.PluginId, apiCmd.Path, c.OrgId, apiCmd.FolderId,
apiCmd.Dashboard, apiCmd.Overwrite, apiCmd.Inputs, c.SignedInUser, hs.DataService) apiCmd.Dashboard, apiCmd.Overwrite, apiCmd.Inputs, c.SignedInUser, hs.DataService)
if err != nil { if err != nil {
return dashboardSaveErrorToApiResponse(err) return hs.dashboardSaveErrorToApiResponse(err)
} }
return response.JSON(200, dashInfo) return response.JSON(200, dashInfo)
@@ -267,8 +266,8 @@ func (hs *HTTPServer) ImportDashboard(c *models.ReqContext, apiCmd dtos.ImportDa
// /api/plugins/:pluginId/metrics // /api/plugins/:pluginId/metrics
func (hs *HTTPServer) CollectPluginMetrics(c *models.ReqContext) response.Response { func (hs *HTTPServer) CollectPluginMetrics(c *models.ReqContext) response.Response {
pluginID := c.Params("pluginId") pluginID := c.Params("pluginId")
plugin, exists := manager.Plugins[pluginID] plugin := hs.PluginManager.GetPlugin(pluginID)
if !exists { if plugin == nil {
return response.Error(404, "Plugin not found", nil) return response.Error(404, "Plugin not found", nil)
} }
+23 -14
View File
@@ -7,19 +7,19 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/teamguardian" "github.com/grafana/grafana/pkg/services/teamguardian"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
// POST /api/teams // POST /api/teams
func (hs *HTTPServer) CreateTeam(c *models.ReqContext, cmd models.CreateTeamCommand) response.Response { func (hs *HTTPServer) CreateTeam(c *models.ReqContext, cmd models.CreateTeamCommand) response.Response {
cmd.OrgId = c.OrgId
if c.OrgRole == models.ROLE_VIEWER { if c.OrgRole == models.ROLE_VIEWER {
return response.Error(403, "Not allowed to create team.", nil) return response.Error(403, "Not allowed to create team.", nil)
} }
if err := hs.Bus.Dispatch(&cmd); err != nil { team, err := createTeam(hs.SQLStore, cmd.Name, cmd.Email, c.OrgId)
if err != nil {
if errors.Is(err, models.ErrTeamNameTaken) { if errors.Is(err, models.ErrTeamNameTaken) {
return response.Error(409, "Team name taken", err) return response.Error(409, "Team name taken", err)
} }
@@ -31,23 +31,17 @@ func (hs *HTTPServer) CreateTeam(c *models.ReqContext, cmd models.CreateTeamComm
// the SignedInUser is an empty struct therefore // the SignedInUser is an empty struct therefore
// an additional check whether it is an actual user is required // an additional check whether it is an actual user is required
if c.SignedInUser.IsRealUser() { if c.SignedInUser.IsRealUser() {
addMemberCmd := models.AddTeamMemberCommand{ if err := addTeamMember(hs.SQLStore, c.SignedInUser.UserId, c.OrgId, team.Id, false,
UserId: c.SignedInUser.UserId, models.PERMISSION_ADMIN); err != nil {
OrgId: cmd.OrgId, c.Logger.Error("Could not add creator to team", "error", err)
TeamId: cmd.Result.Id,
Permission: models.PERMISSION_ADMIN,
}
if err := hs.Bus.Dispatch(&addMemberCmd); err != nil {
c.Logger.Error("Could not add creator to team.", "error", err)
} }
} else { } else {
c.Logger.Warn("Could not add creator to team because is not a real user.") c.Logger.Warn("Could not add creator to team because is not a real user")
} }
} }
return response.JSON(200, &util.DynMap{ return response.JSON(200, &util.DynMap{
"teamId": cmd.Result.Id, "teamId": team.Id,
"message": "Team created", "message": "Team created",
}) })
} }
@@ -175,3 +169,18 @@ func (hs *HTTPServer) UpdateTeamPreferences(c *models.ReqContext, dtoCmd dtos.Up
return updatePreferencesFor(orgId, 0, teamId, &dtoCmd) return updatePreferencesFor(orgId, 0, teamId, &dtoCmd)
} }
// createTeam creates a team.
//
// Stubbable by tests.
var createTeam = func(sqlStore *sqlstore.SQLStore, name, email string, orgID int64) (models.Team, error) {
return sqlStore.CreateTeam(name, email, orgID)
}
// addTeamMember adds a team member.
//
// Stubbable by tests.
var addTeamMember = func(sqlStore *sqlstore.SQLStore, userID, orgID, teamID int64, isExternal bool,
permission models.PermissionType) error {
return sqlStore.AddTeamMember(userID, orgID, teamID, isExternal, permission)
}
+20 -10
View File
@@ -3,6 +3,7 @@ package api
import ( import (
"testing" "testing"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
macaron "gopkg.in/macaron.v1" macaron "gopkg.in/macaron.v1"
@@ -98,20 +99,29 @@ func TestTeamAPIEndpoint(t *testing.T) {
teamName := "team foo" teamName := "team foo"
createTeamCalled := 0 // TODO: Use a fake SQLStore when it's represented by an interface
bus.AddHandler("test", func(cmd *models.CreateTeamCommand) error { origCreateTeam := createTeam
createTeamCalled += 1 origAddTeamMember := addTeamMember
cmd.Result = models.Team{Name: teamName, Id: 42} t.Cleanup(func() {
return nil createTeam = origCreateTeam
addTeamMember = origAddTeamMember
}) })
createTeamCalled := 0
createTeam = func(sqlStore *sqlstore.SQLStore, name, email string, orgID int64) (models.Team, error) {
createTeamCalled++
return models.Team{Name: teamName, Id: 42}, nil
}
addTeamMemberCalled := 0 addTeamMemberCalled := 0
bus.AddHandler("test", func(cmd *models.AddTeamMemberCommand) error { addTeamMember = func(sqlStore *sqlstore.SQLStore, userID, orgID, teamID int64, isExternal bool,
addTeamMemberCalled += 1 permission models.PermissionType) error {
addTeamMemberCalled++
return nil return nil
}) }
req, _ := http.NewRequest("POST", "/api/teams", nil) req, err := http.NewRequest("POST", "/api/teams", nil)
require.NoError(t, err)
t.Run("with no real signed in user", func(t *testing.T) { t.Run("with no real signed in user", func(t *testing.T) {
stub := &testLogger{} stub := &testLogger{}
@@ -128,7 +138,7 @@ func TestTeamAPIEndpoint(t *testing.T) {
assert.Equal(t, createTeamCalled, 1) assert.Equal(t, createTeamCalled, 1)
assert.Equal(t, addTeamMemberCalled, 0) assert.Equal(t, addTeamMemberCalled, 0)
assert.True(t, stub.warnCalled) assert.True(t, stub.warnCalled)
assert.Equal(t, stub.warnMessage, "Could not add creator to team because is not a real user.") assert.Equal(t, stub.warnMessage, "Could not add creator to team because is not a real user")
}) })
t.Run("with real signed in user", func(t *testing.T) { t.Run("with real signed in user", func(t *testing.T) {
+16
View File
@@ -0,0 +1,16 @@
package dashboards
import "github.com/grafana/grafana/pkg/models"
// Store is a dashboard store.
type Store interface {
// ValidateDashboardBeforeSave validates a dashboard before save.
ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error)
GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)
UpdateDashboardACL(uid int64, items []*models.DashboardAcl) error
// SaveAlerts saves dashboard alerts.
SaveAlerts(dashID int64, alerts []*models.Alert) error
}
+2
View File
@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
@@ -38,6 +39,7 @@ type UsageStatsService struct {
SQLStore *sqlstore.SQLStore `inject:""` SQLStore *sqlstore.SQLStore `inject:""`
AlertingUsageStats alerting.UsageStatsQuerier `inject:""` AlertingUsageStats alerting.UsageStatsQuerier `inject:""`
License models.Licensing `inject:""` License models.Licensing `inject:""`
PluginManager plugins.Manager `inject:""`
log log.Logger log log.Logger
+3 -3
View File
@@ -58,7 +58,7 @@ func (uss *UsageStatsService) GetUsageReport(ctx context.Context) (UsageReport,
metrics["stats.playlist.count"] = statsQuery.Result.Playlists metrics["stats.playlist.count"] = statsQuery.Result.Playlists
metrics["stats.plugins.apps.count"] = len(manager.Apps) metrics["stats.plugins.apps.count"] = len(manager.Apps)
metrics["stats.plugins.panels.count"] = len(manager.Panels) metrics["stats.plugins.panels.count"] = len(manager.Panels)
metrics["stats.plugins.datasources.count"] = len(manager.DataSources) metrics["stats.plugins.datasources.count"] = uss.PluginManager.DataSourceCount()
metrics["stats.alerts.count"] = statsQuery.Result.Alerts metrics["stats.alerts.count"] = statsQuery.Result.Alerts
metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers
metrics["stats.datasources.count"] = statsQuery.Result.Datasources metrics["stats.datasources.count"] = statsQuery.Result.Datasources
@@ -329,8 +329,8 @@ func (uss *UsageStatsService) updateTotalStats() {
} }
func (uss *UsageStatsService) shouldBeReported(dsType string) bool { func (uss *UsageStatsService) shouldBeReported(dsType string) bool {
ds, ok := manager.DataSources[dsType] ds := uss.PluginManager.GetDataSource(dsType)
if !ok { if ds == nil {
return false return false
} }
+38 -23
View File
@@ -296,7 +296,7 @@ func TestMetrics(t *testing.T) {
assert.Equal(t, getSystemStatsQuery.Result.Playlists, metrics.Get("stats.playlist.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.Playlists, metrics.Get("stats.playlist.count").MustInt64())
assert.Equal(t, len(manager.Apps), metrics.Get("stats.plugins.apps.count").MustInt()) assert.Equal(t, len(manager.Apps), metrics.Get("stats.plugins.apps.count").MustInt())
assert.Equal(t, len(manager.Panels), metrics.Get("stats.plugins.panels.count").MustInt()) assert.Equal(t, len(manager.Panels), metrics.Get("stats.plugins.panels.count").MustInt())
assert.Equal(t, len(manager.DataSources), metrics.Get("stats.plugins.datasources.count").MustInt()) assert.Equal(t, uss.PluginManager.DataSourceCount(), metrics.Get("stats.plugins.datasources.count").MustInt())
assert.Equal(t, getSystemStatsQuery.Result.Alerts, metrics.Get("stats.alerts.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.Alerts, metrics.Get("stats.alerts.count").MustInt64())
assert.Equal(t, getSystemStatsQuery.Result.ActiveUsers, metrics.Get("stats.active_users.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.ActiveUsers, metrics.Get("stats.active_users.count").MustInt64())
assert.Equal(t, getSystemStatsQuery.Result.Datasources, metrics.Get("stats.datasources.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.Datasources, metrics.Get("stats.datasources.count").MustInt64())
@@ -542,37 +542,51 @@ func (aum *alertingUsageMock) QueryUsageStats() (*alerting.UsageStats, error) {
}, nil }, nil
} }
type fakePluginManager struct {
manager.PluginManager
dataSources map[string]*plugins.DataSourcePlugin
}
func (pm fakePluginManager) DataSourceCount() int {
return len(pm.dataSources)
}
func (pm fakePluginManager) GetDataSource(id string) *plugins.DataSourcePlugin {
return pm.dataSources[id]
}
func setupSomeDataSourcePlugins(t *testing.T, uss *UsageStatsService) { func setupSomeDataSourcePlugins(t *testing.T, uss *UsageStatsService) {
t.Helper() t.Helper()
originalDataSources := manager.DataSources uss.PluginManager = &fakePluginManager{
t.Cleanup(func() { manager.DataSources = originalDataSources }) dataSources: map[string]*plugins.DataSourcePlugin{
manager.DataSources = map[string]*plugins.DataSourcePlugin{ models.DS_ES: {
models.DS_ES: { FrontendPluginBase: plugins.FrontendPluginBase{
FrontendPluginBase: plugins.FrontendPluginBase{ PluginBase: plugins.PluginBase{
PluginBase: plugins.PluginBase{ Signature: "internal",
Signature: "internal", },
}, },
}, },
}, models.DS_PROMETHEUS: {
models.DS_PROMETHEUS: { FrontendPluginBase: plugins.FrontendPluginBase{
FrontendPluginBase: plugins.FrontendPluginBase{ PluginBase: plugins.PluginBase{
PluginBase: plugins.PluginBase{ Signature: "internal",
Signature: "internal", },
}, },
}, },
}, models.DS_GRAPHITE: {
models.DS_GRAPHITE: { FrontendPluginBase: plugins.FrontendPluginBase{
FrontendPluginBase: plugins.FrontendPluginBase{ PluginBase: plugins.PluginBase{
PluginBase: plugins.PluginBase{ Signature: "internal",
Signature: "internal", },
}, },
}, },
}, models.DS_MYSQL: {
models.DS_MYSQL: { FrontendPluginBase: plugins.FrontendPluginBase{
FrontendPluginBase: plugins.FrontendPluginBase{ PluginBase: plugins.PluginBase{
PluginBase: plugins.PluginBase{ Signature: "internal",
Signature: "internal", },
}, },
}, },
}, },
@@ -595,5 +609,6 @@ func createService(t *testing.T, cfg setting.Cfg) *UsageStatsService {
License: &licensing.OSSLicensingService{}, License: &licensing.OSSLicensingService{},
AlertingUsageStats: &alertingUsageMock{}, AlertingUsageStats: &alertingUsageMock{},
externalMetrics: make(map[string]MetricFunc), externalMetrics: make(map[string]MetricFunc),
PluginManager: &fakePluginManager{},
} }
} }
-15
View File
@@ -209,18 +209,3 @@ type AlertStateInfoDTO struct {
State AlertStateType `json:"state"` State AlertStateType `json:"state"`
NewStateDate time.Time `json:"newStateDate"` NewStateDate time.Time `json:"newStateDate"`
} }
// "Internal" commands
type UpdateDashboardAlertsCommand struct {
OrgId int64
Dashboard *Dashboard
User *SignedInUser
}
type ValidateDashboardAlertsCommand struct {
UserId int64
OrgId int64
Dashboard *Dashboard
User *SignedInUser
}
-9
View File
@@ -95,15 +95,6 @@ func (dto *DashboardAclInfoDTO) IsDuplicateOf(other *DashboardAclInfoDTO) bool {
return dto.hasSameRoleAs(other) || dto.hasSameUserAs(other) || dto.hasSameTeamAs(other) return dto.hasSameRoleAs(other) || dto.hasSameUserAs(other) || dto.hasSameTeamAs(other)
} }
//
// COMMANDS
//
type UpdateDashboardAclCommand struct {
DashboardID int64
Items []*DashboardAcl
}
// //
// QUERIES // QUERIES
// //
-12
View File
@@ -367,13 +367,6 @@ type DeleteDashboardCommand struct {
OrgId int64 OrgId int64
} }
type ValidateDashboardBeforeSaveCommand struct {
OrgId int64
Dashboard *Dashboard
Overwrite bool
Result *ValidateDashboardBeforeSaveResult
}
type DeleteOrphanedProvisionedDashboardsCommand struct { type DeleteOrphanedProvisionedDashboardsCommand struct {
ReaderNames []string ReaderNames []string
} }
@@ -430,11 +423,6 @@ type GetProvisionedDashboardDataByIdQuery struct {
Result *DashboardProvisioning Result *DashboardProvisioning
} }
type GetProvisionedDashboardDataQuery struct {
Name string
Result []*DashboardProvisioning
}
type GetDashboardsBySlugQuery struct { type GetDashboardsBySlugQuery struct {
OrgId int64 OrgId int64
Slug string Slug string
-17
View File
@@ -32,23 +32,6 @@ type Folder struct {
HasAcl bool HasAcl bool
} }
// GetDashboardModel turns the command into the saveable model
func (cmd *CreateFolderCommand) GetDashboardModel(orgId int64, userId int64) *Dashboard {
dashFolder := NewDashboardFolder(strings.TrimSpace(cmd.Title))
dashFolder.OrgId = orgId
dashFolder.SetUid(strings.TrimSpace(cmd.Uid))
if userId == 0 {
userId = -1
}
dashFolder.CreatedBy = userId
dashFolder.UpdatedBy = userId
dashFolder.UpdateSlug()
return dashFolder
}
// UpdateDashboardModel updates an existing model from command into model for update // UpdateDashboardModel updates an existing model from command into model for update
func (cmd *UpdateFolderCommand) UpdateDashboardModel(dashFolder *Dashboard, orgId int64, userId int64) { func (cmd *UpdateFolderCommand) UpdateDashboardModel(dashFolder *Dashboard, orgId int64, userId int64) {
dashFolder.OrgId = orgId dashFolder.OrgId = orgId
-4
View File
@@ -53,10 +53,6 @@ func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() securejsondata.SecureJ
// --------------------- // ---------------------
// QUERIES // QUERIES
type GetPluginSettingsQuery struct {
OrgId int64
Result []*PluginSettingInfoDTO
}
type PluginSettingInfoDTO struct { type PluginSettingInfoDTO struct {
OrgId int64 OrgId int64
+45
View File
@@ -3,10 +3,55 @@ package plugins
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
) )
// Manager is the plugin manager service interface.
type Manager interface {
// Renderer gets the renderer plugin.
Renderer() *RendererPlugin
// GetDataSource gets a data source plugin with a certain ID.
GetDataSource(id string) *DataSourcePlugin
// GetDataPlugin gets a data plugin with a certain ID.
GetDataPlugin(id string) DataPlugin
// GetPlugin gets a plugin with a certain ID.
GetPlugin(id string) *PluginBase
// DataSourceCount gets the number of data sources.
DataSourceCount() int
// DataSources gets all data sources.
DataSources() []*DataSourcePlugin
// GetEnabledPlugins gets enabled plugins.
GetEnabledPlugins(orgID int64) (*EnabledPlugins, error)
// GrafanaLatestVersion gets the latest Grafana version.
GrafanaLatestVersion() string
// GrafanaHasUpdate returns whether Grafana has an update.
GrafanaHasUpdate() bool
// Plugins gets all plugins.
Plugins() []*PluginBase
// GetPluginSettings gets settings for a certain plugin.
GetPluginSettings(orgID int64) (map[string]*models.PluginSettingInfoDTO, error)
// GetPluginDashboards gets dashboards for a certain org/plugin.
GetPluginDashboards(orgID int64, pluginID string) ([]*PluginDashboardInfoDTO, error)
// GetPluginMarkdown gets markdown for a certain plugin/name.
GetPluginMarkdown(pluginID string, name string) ([]byte, error)
// ImportDashboard imports a dashboard.
ImportDashboard(pluginID, path string, orgID, folderID int64, dashboardModel *simplejson.Json,
overwrite bool, inputs []ImportDashboardInput, user *models.SignedInUser,
requestHandler DataRequestHandler) (PluginDashboardInfoDTO, error)
// ScanningErrors returns plugin scanning errors encountered.
ScanningErrors() []PluginError
}
type ImportDashboardInput struct {
Type string `json:"type"`
PluginId string `json:"pluginId"`
Name string `json:"name"`
Value string `json:"value"`
}
// DataRequestHandler is a data request handler interface. // DataRequestHandler is a data request handler interface.
type DataRequestHandler interface { type DataRequestHandler interface {
// HandleRequest handles a data request.
HandleRequest(context.Context, *models.DataSource, DataQuery) (DataResponse, error) HandleRequest(context.Context, *models.DataSource, DataQuery) (DataResponse, error)
} }
+4 -11
View File
@@ -13,13 +13,6 @@ import (
var varRegex = regexp.MustCompile(`(\$\{.+?\})`) var varRegex = regexp.MustCompile(`(\$\{.+?\})`)
type ImportDashboardInput struct {
Type string `json:"type"`
PluginId string `json:"pluginId"`
Name string `json:"name"`
Value string `json:"value"`
}
type DashboardInputMissingError struct { type DashboardInputMissingError struct {
VariableName string VariableName string
} }
@@ -29,7 +22,7 @@ func (e DashboardInputMissingError) Error() string {
} }
func (pm *PluginManager) ImportDashboard(pluginID, path string, orgID, folderID int64, dashboardModel *simplejson.Json, func (pm *PluginManager) ImportDashboard(pluginID, path string, orgID, folderID int64, dashboardModel *simplejson.Json,
overwrite bool, inputs []ImportDashboardInput, user *models.SignedInUser, overwrite bool, inputs []plugins.ImportDashboardInput, user *models.SignedInUser,
requestHandler plugins.DataRequestHandler) (plugins.PluginDashboardInfoDTO, error) { requestHandler plugins.DataRequestHandler) (plugins.PluginDashboardInfoDTO, error) {
var dashboard *models.Dashboard var dashboard *models.Dashboard
if pluginID != "" { if pluginID != "" {
@@ -67,7 +60,7 @@ func (pm *PluginManager) ImportDashboard(pluginID, path string, orgID, folderID
User: user, User: user,
} }
savedDash, err := dashboards.NewService().ImportDashboard(dto) savedDash, err := dashboards.NewService(pm.SQLStore).ImportDashboard(dto)
if err != nil { if err != nil {
return plugins.PluginDashboardInfoDTO{}, err return plugins.PluginDashboardInfoDTO{}, err
} }
@@ -89,12 +82,12 @@ func (pm *PluginManager) ImportDashboard(pluginID, path string, orgID, folderID
type DashTemplateEvaluator struct { type DashTemplateEvaluator struct {
template *simplejson.Json template *simplejson.Json
inputs []ImportDashboardInput inputs []plugins.ImportDashboardInput
variables map[string]string variables map[string]string
result *simplejson.Json result *simplejson.Json
} }
func (e *DashTemplateEvaluator) findInput(varName string, varType string) *ImportDashboardInput { func (e *DashTemplateEvaluator) findInput(varName string, varType string) *plugins.ImportDashboardInput {
for _, input := range e.inputs { for _, input := range e.inputs {
if varType == input.Type && (input.Name == varName || input.Name == "*") { if varType == input.Type && (input.Name == varName || input.Name == "*") {
return &input return &input
+3 -2
View File
@@ -6,6 +6,7 @@ import (
"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/plugins"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -21,7 +22,7 @@ func TestDashboardImport(t *testing.T) {
dashboards.MockDashboardService(mock) dashboards.MockDashboardService(mock)
info, err := pm.ImportDashboard("test-app", "dashboards/connections.json", 1, 0, nil, false, info, err := pm.ImportDashboard("test-app", "dashboards/connections.json", 1, 0, nil, false,
[]ImportDashboardInput{ []plugins.ImportDashboardInput{
{Name: "*", Type: "datasource", Value: "graphite"}, {Name: "*", Type: "datasource", Value: "graphite"},
}, &models.SignedInUser{UserId: 1, OrgRole: models.ROLE_ADMIN}, nil) }, &models.SignedInUser{UserId: 1, OrgRole: models.ROLE_ADMIN}, nil)
require.NoError(t, err) require.NoError(t, err)
@@ -58,7 +59,7 @@ func TestDashboardImport(t *testing.T) {
evaluator := &DashTemplateEvaluator{ evaluator := &DashTemplateEvaluator{
template: template, template: template,
inputs: []ImportDashboardInput{ inputs: []plugins.ImportDashboardInput{
{Name: "*", Type: "datasource", Value: "my-server"}, {Name: "*", Type: "datasource", Value: "my-server"},
}, },
} }
+2 -2
View File
@@ -11,7 +11,7 @@ import (
) )
func (pm *PluginManager) GetPluginDashboards(orgId int64, pluginId string) ([]*plugins.PluginDashboardInfoDTO, error) { func (pm *PluginManager) GetPluginDashboards(orgId int64, pluginId string) ([]*plugins.PluginDashboardInfoDTO, error) {
plugin, exists := Plugins[pluginId] plugin, exists := pm.plugins[pluginId]
if !exists { if !exists {
return nil, plugins.PluginNotFoundError{PluginID: pluginId} return nil, plugins.PluginNotFoundError{PluginID: pluginId}
} }
@@ -71,7 +71,7 @@ func (pm *PluginManager) GetPluginDashboards(orgId int64, pluginId string) ([]*p
} }
func (pm *PluginManager) LoadPluginDashboard(pluginId, path string) (*models.Dashboard, error) { func (pm *PluginManager) LoadPluginDashboard(pluginId, path string) (*models.Dashboard, error) {
plugin, exists := Plugins[pluginId] plugin, exists := pm.plugins[pluginId]
if !exists { if !exists {
return nil, plugins.PluginNotFoundError{PluginID: pluginId} return nil, plugins.PluginNotFoundError{PluginID: pluginId}
} }
+67 -30
View File
@@ -21,19 +21,17 @@ import (
"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/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
) )
var ( var (
DataSources map[string]*plugins.DataSourcePlugin
Panels map[string]*plugins.PanelPlugin Panels map[string]*plugins.PanelPlugin
StaticRoutes []*plugins.PluginStaticRoute StaticRoutes []*plugins.PluginStaticRoute
Apps map[string]*plugins.AppPlugin Apps map[string]*plugins.AppPlugin
Plugins map[string]*plugins.PluginBase
PluginTypes map[string]interface{} PluginTypes map[string]interface{}
Renderer *plugins.RendererPlugin
plog log.Logger plog log.Logger
) )
@@ -54,30 +52,36 @@ type PluginScanner struct {
type PluginManager struct { type PluginManager struct {
BackendPluginManager backendplugin.Manager `inject:""` BackendPluginManager backendplugin.Manager `inject:""`
Cfg *setting.Cfg `inject:""` Cfg *setting.Cfg `inject:""`
SQLStore *sqlstore.SQLStore `inject:""`
log log.Logger log log.Logger
scanningErrors []error scanningErrors []error
// AllowUnsignedPluginsCondition changes the policy for allowing unsigned plugins. Signature validation only runs when plugins are starting // AllowUnsignedPluginsCondition changes the policy for allowing unsigned plugins. Signature validation only runs when plugins are starting
// and running plugins will not be terminated if they violate the new policy. // and running plugins will not be terminated if they violate the new policy.
AllowUnsignedPluginsCondition unsignedPluginConditionFunc AllowUnsignedPluginsCondition unsignedPluginConditionFunc
GrafanaLatestVersion string grafanaLatestVersion string
GrafanaHasUpdate bool grafanaHasUpdate bool
pluginScanningErrors map[string]plugins.PluginError pluginScanningErrors map[string]plugins.PluginError
renderer *plugins.RendererPlugin
dataSources map[string]*plugins.DataSourcePlugin
plugins map[string]*plugins.PluginBase
} }
func init() { func init() {
registry.RegisterService(&PluginManager{}) registry.RegisterService(&PluginManager{
dataSources: map[string]*plugins.DataSourcePlugin{},
})
} }
func (pm *PluginManager) Init() error { func (pm *PluginManager) Init() error {
pm.log = log.New("plugins") pm.log = log.New("plugins")
plog = log.New("plugins") plog = log.New("plugins")
DataSources = map[string]*plugins.DataSourcePlugin{}
StaticRoutes = []*plugins.PluginStaticRoute{} StaticRoutes = []*plugins.PluginStaticRoute{}
Panels = map[string]*plugins.PanelPlugin{} Panels = map[string]*plugins.PanelPlugin{}
Apps = map[string]*plugins.AppPlugin{} Apps = map[string]*plugins.AppPlugin{}
Plugins = map[string]*plugins.PluginBase{} pm.plugins = map[string]*plugins.PluginBase{}
PluginTypes = map[string]interface{}{ PluginTypes = map[string]interface{}{
"panel": plugins.PanelPlugin{}, "panel": plugins.PanelPlugin{},
"datasource": plugins.DataSourcePlugin{}, "datasource": plugins.DataSourcePlugin{},
@@ -134,22 +138,22 @@ func (pm *PluginManager) Init() error {
StaticRoutes = append(StaticRoutes, staticRoutes...) StaticRoutes = append(StaticRoutes, staticRoutes...)
} }
for _, ds := range DataSources { for _, ds := range pm.dataSources {
staticRoutes := ds.InitFrontendPlugin(pm.Cfg) staticRoutes := ds.InitFrontendPlugin(pm.Cfg)
StaticRoutes = append(StaticRoutes, staticRoutes...) StaticRoutes = append(StaticRoutes, staticRoutes...)
} }
for _, app := range Apps { for _, app := range Apps {
staticRoutes := app.InitApp(Panels, DataSources, pm.Cfg) staticRoutes := app.InitApp(Panels, pm.dataSources, pm.Cfg)
StaticRoutes = append(StaticRoutes, staticRoutes...) StaticRoutes = append(StaticRoutes, staticRoutes...)
} }
if Renderer != nil { if pm.renderer != nil {
staticRoutes := Renderer.InitFrontendPlugin(pm.Cfg) staticRoutes := pm.renderer.InitFrontendPlugin(pm.Cfg)
StaticRoutes = append(StaticRoutes, staticRoutes...) StaticRoutes = append(StaticRoutes, staticRoutes...)
} }
for _, p := range Plugins { for _, p := range pm.plugins {
if p.IsCorePlugin { if p.IsCorePlugin {
p.Signature = plugins.PluginSignatureInternal p.Signature = plugins.PluginSignatureInternal
} else { } else {
@@ -178,6 +182,48 @@ func (pm *PluginManager) Run(ctx context.Context) error {
return ctx.Err() return ctx.Err()
} }
func (pm *PluginManager) Renderer() *plugins.RendererPlugin {
return pm.renderer
}
func (pm *PluginManager) GetDataSource(id string) *plugins.DataSourcePlugin {
return pm.dataSources[id]
}
func (pm *PluginManager) DataSources() []*plugins.DataSourcePlugin {
var rslt []*plugins.DataSourcePlugin
for _, ds := range pm.dataSources {
rslt = append(rslt, ds)
}
return rslt
}
func (pm *PluginManager) DataSourceCount() int {
return len(pm.dataSources)
}
func (pm *PluginManager) Plugins() []*plugins.PluginBase {
var rslt []*plugins.PluginBase
for _, p := range pm.plugins {
rslt = append(rslt, p)
}
return rslt
}
func (pm *PluginManager) GetPlugin(id string) *plugins.PluginBase {
return pm.plugins[id]
}
func (pm *PluginManager) GrafanaLatestVersion() string {
return pm.grafanaLatestVersion
}
func (pm *PluginManager) GrafanaHasUpdate() bool {
return pm.grafanaHasUpdate
}
// scanPluginPaths scans configured plugin paths. // scanPluginPaths scans configured plugin paths.
func (pm *PluginManager) scanPluginPaths() error { func (pm *PluginManager) scanPluginPaths() error {
for pluginID, settings := range pm.Cfg.PluginSettings { for pluginID, settings := range pm.Cfg.PluginSettings {
@@ -315,13 +361,13 @@ func (pm *PluginManager) loadPlugin(jsonParser *json.Decoder, pluginBase *plugin
var pb *plugins.PluginBase var pb *plugins.PluginBase
switch p := plug.(type) { switch p := plug.(type) {
case *plugins.DataSourcePlugin: case *plugins.DataSourcePlugin:
DataSources[p.Id] = p pm.dataSources[p.Id] = p
pb = &p.PluginBase pb = &p.PluginBase
case *plugins.PanelPlugin: case *plugins.PanelPlugin:
Panels[p.Id] = p Panels[p.Id] = p
pb = &p.PluginBase pb = &p.PluginBase
case *plugins.RendererPlugin: case *plugins.RendererPlugin:
Renderer = p pm.renderer = p
pb = &p.PluginBase pb = &p.PluginBase
case *plugins.AppPlugin: case *plugins.AppPlugin:
Apps[p.Id] = p Apps[p.Id] = p
@@ -330,7 +376,7 @@ func (pm *PluginManager) loadPlugin(jsonParser *json.Decoder, pluginBase *plugin
panic(fmt.Sprintf("Unrecognized plugin type %T", plug)) panic(fmt.Sprintf("Unrecognized plugin type %T", plug))
} }
if p, exists := Plugins[pb.Id]; exists { if p, exists := pm.plugins[pb.Id]; exists {
pm.log.Warn("Plugin is duplicate", "id", pb.Id) pm.log.Warn("Plugin is duplicate", "id", pb.Id)
scanner.errors = append(scanner.errors, plugins.DuplicatePluginError{Plugin: pb, ExistingPlugin: p}) scanner.errors = append(scanner.errors, plugins.DuplicatePluginError{Plugin: pb, ExistingPlugin: p})
return nil return nil
@@ -360,20 +406,11 @@ func (pm *PluginManager) loadPlugin(jsonParser *json.Decoder, pluginBase *plugin
pb.SignatureType = pluginBase.SignatureType pb.SignatureType = pluginBase.SignatureType
pb.SignatureOrg = pluginBase.SignatureOrg pb.SignatureOrg = pluginBase.SignatureOrg
Plugins[pb.Id] = pb pm.plugins[pb.Id] = pb
pm.log.Debug("Successfully added plugin", "id", pb.Id) pm.log.Debug("Successfully added plugin", "id", pb.Id)
return nil return nil
} }
// GetDatasource returns a datasource based on passed pluginID if it exists
//
// This function fetches the datasource from the global variable DataSources in this package.
// Rather then refactor all dependencies on the global variable we can use this as an transition.
func (pm *PluginManager) GetDatasource(pluginID string) (*plugins.DataSourcePlugin, bool) {
ds, exists := DataSources[pluginID]
return ds, exists
}
func (s *PluginScanner) walker(currentPath string, f os.FileInfo, err error) error { func (s *PluginScanner) walker(currentPath string, f os.FileInfo, err error) error {
// We scan all the subfolders for plugin.json (with some exceptions) so that we also load embedded plugins, for // We scan all the subfolders for plugin.json (with some exceptions) so that we also load embedded plugins, for
// example https://github.com/raintank/worldping-app/tree/master/dist/grafana-worldmap-panel worldmap panel plugin // example https://github.com/raintank/worldping-app/tree/master/dist/grafana-worldmap-panel worldmap panel plugin
@@ -545,7 +582,7 @@ func (pm *PluginManager) ScanningErrors() []plugins.PluginError {
} }
func (pm *PluginManager) GetPluginMarkdown(pluginId string, name string) ([]byte, error) { func (pm *PluginManager) GetPluginMarkdown(pluginId string, name string) ([]byte, error) {
plug, exists := Plugins[pluginId] plug, exists := pm.plugins[pluginId]
if !exists { if !exists {
return nil, plugins.PluginNotFoundError{PluginID: pluginId} return nil, plugins.PluginNotFoundError{PluginID: pluginId}
} }
@@ -600,14 +637,14 @@ func collectPluginFilesWithin(rootDir string) ([]string, error) {
} }
// GetDataPlugin gets a DataPlugin with a certain name. If none is found, nil is returned. // GetDataPlugin gets a DataPlugin with a certain name. If none is found, nil is returned.
func (pm *PluginManager) GetDataPlugin(pluginID string) plugins.DataPlugin { func (pm *PluginManager) GetDataPlugin(id string) plugins.DataPlugin {
if p, exists := DataSources[pluginID]; exists && p.CanHandleDataQueries() { if p, exists := pm.dataSources[id]; exists && p.CanHandleDataQueries() {
return p return p
} }
// XXX: Might other plugins implement DataPlugin? // XXX: Might other plugins implement DataPlugin?
p := pm.BackendPluginManager.GetDataPlugin(pluginID) p := pm.BackendPluginManager.GetDataPlugin(id)
if p != nil { if p != nil {
return p.(plugins.DataPlugin) return p.(plugins.DataPlugin)
} }
+24 -23
View File
@@ -30,9 +30,9 @@ func TestPluginManager_Init(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Empty(t, pm.scanningErrors) assert.Empty(t, pm.scanningErrors)
assert.Greater(t, len(DataSources), 1) assert.Greater(t, len(pm.dataSources), 1)
assert.Greater(t, len(Panels), 1) assert.Greater(t, len(Panels), 1)
assert.Equal(t, "app/plugins/datasource/graphite/module", DataSources["graphite"].Module) assert.Equal(t, "app/plugins/datasource/graphite/module", pm.dataSources["graphite"].Module)
assert.NotEmpty(t, Apps) assert.NotEmpty(t, Apps)
assert.Equal(t, "public/plugins/test-app/img/logo_large.png", Apps["test-app"].Info.Logos.Large) assert.Equal(t, "public/plugins/test-app/img/logo_large.png", Apps["test-app"].Info.Logos.Large)
assert.Equal(t, "public/plugins/test-app/img/screenshot2.png", Apps["test-app"].Info.Screenshots[1].Path) assert.Equal(t, "public/plugins/test-app/img/screenshot2.png", Apps["test-app"].Info.Screenshots[1].Path)
@@ -114,15 +114,15 @@ func TestPluginManager_Init(t *testing.T) {
require.Empty(t, pm.scanningErrors) require.Empty(t, pm.scanningErrors)
const pluginID = "test" const pluginID = "test"
assert.NotNil(t, Plugins[pluginID]) assert.NotNil(t, pm.plugins[pluginID])
assert.Equal(t, "datasource", Plugins[pluginID].Type) assert.Equal(t, "datasource", pm.plugins[pluginID].Type)
assert.Equal(t, "Test", Plugins[pluginID].Name) assert.Equal(t, "Test", pm.plugins[pluginID].Name)
assert.Equal(t, pluginID, Plugins[pluginID].Id) assert.Equal(t, pluginID, pm.plugins[pluginID].Id)
assert.Equal(t, "1.0.0", Plugins[pluginID].Info.Version) assert.Equal(t, "1.0.0", pm.plugins[pluginID].Info.Version)
assert.Equal(t, plugins.PluginSignatureValid, Plugins[pluginID].Signature) assert.Equal(t, plugins.PluginSignatureValid, pm.plugins[pluginID].Signature)
assert.Equal(t, plugins.GrafanaType, Plugins[pluginID].SignatureType) assert.Equal(t, plugins.GrafanaType, pm.plugins[pluginID].SignatureType)
assert.Equal(t, "Grafana Labs", Plugins[pluginID].SignatureOrg) assert.Equal(t, "Grafana Labs", pm.plugins[pluginID].SignatureOrg)
assert.False(t, Plugins[pluginID].IsCorePlugin) assert.False(t, pm.plugins[pluginID].IsCorePlugin)
}) })
t.Run("With back-end plugin with invalid v2 private signature (mismatched root URL)", func(t *testing.T) { t.Run("With back-end plugin with invalid v2 private signature (mismatched root URL)", func(t *testing.T) {
@@ -139,7 +139,7 @@ func TestPluginManager_Init(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []error{fmt.Errorf(`plugin "test" has an invalid signature`)}, pm.scanningErrors) assert.Equal(t, []error{fmt.Errorf(`plugin "test" has an invalid signature`)}, pm.scanningErrors)
assert.Nil(t, Plugins[("test")]) assert.Nil(t, pm.plugins[("test")])
}) })
t.Run("With back-end plugin with valid v2 private signature", func(t *testing.T) { t.Run("With back-end plugin with valid v2 private signature", func(t *testing.T) {
@@ -157,15 +157,15 @@ func TestPluginManager_Init(t *testing.T) {
require.Empty(t, pm.scanningErrors) require.Empty(t, pm.scanningErrors)
const pluginID = "test" const pluginID = "test"
assert.NotNil(t, Plugins[pluginID]) assert.NotNil(t, pm.plugins[pluginID])
assert.Equal(t, "datasource", Plugins[pluginID].Type) assert.Equal(t, "datasource", pm.plugins[pluginID].Type)
assert.Equal(t, "Test", Plugins[pluginID].Name) assert.Equal(t, "Test", pm.plugins[pluginID].Name)
assert.Equal(t, pluginID, Plugins[pluginID].Id) assert.Equal(t, pluginID, pm.plugins[pluginID].Id)
assert.Equal(t, "1.0.0", Plugins[pluginID].Info.Version) assert.Equal(t, "1.0.0", pm.plugins[pluginID].Info.Version)
assert.Equal(t, plugins.PluginSignatureValid, Plugins[pluginID].Signature) assert.Equal(t, plugins.PluginSignatureValid, pm.plugins[pluginID].Signature)
assert.Equal(t, plugins.PrivateType, Plugins[pluginID].SignatureType) assert.Equal(t, plugins.PrivateType, pm.plugins[pluginID].SignatureType)
assert.Equal(t, "Will Browne", Plugins[pluginID].SignatureOrg) assert.Equal(t, "Will Browne", pm.plugins[pluginID].SignatureOrg)
assert.False(t, Plugins[pluginID].IsCorePlugin) assert.False(t, pm.plugins[pluginID].IsCorePlugin)
}) })
t.Run("With back-end plugin with modified v2 signature (missing file from plugin dir)", func(t *testing.T) { t.Run("With back-end plugin with modified v2 signature (missing file from plugin dir)", func(t *testing.T) {
@@ -181,7 +181,7 @@ func TestPluginManager_Init(t *testing.T) {
err := pm.Init() err := pm.Init()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []error{fmt.Errorf(`plugin "test"'s signature has been modified`)}, pm.scanningErrors) assert.Equal(t, []error{fmt.Errorf(`plugin "test"'s signature has been modified`)}, pm.scanningErrors)
assert.Nil(t, Plugins[("test")]) assert.Nil(t, pm.plugins[("test")])
}) })
t.Run("With back-end plugin with modified v2 signature (unaccounted file in plugin dir)", func(t *testing.T) { t.Run("With back-end plugin with modified v2 signature (unaccounted file in plugin dir)", func(t *testing.T) {
@@ -197,7 +197,7 @@ func TestPluginManager_Init(t *testing.T) {
err := pm.Init() err := pm.Init()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []error{fmt.Errorf(`plugin "test"'s signature has been modified`)}, pm.scanningErrors) assert.Equal(t, []error{fmt.Errorf(`plugin "test"'s signature has been modified`)}, pm.scanningErrors)
assert.Nil(t, Plugins[("test")]) assert.Nil(t, pm.plugins[("test")])
}) })
} }
@@ -260,6 +260,7 @@ func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager {
StaticRootPath: staticRootPath, StaticRootPath: staticRootPath,
}, },
BackendPluginManager: &fakeBackendPluginManager{}, BackendPluginManager: &fakeBackendPluginManager{},
dataSources: map[string]*plugins.DataSourcePlugin{},
} }
for _, cb := range cbs { for _, cb := range cbs {
cb(pm) cb(pm)
+7 -8
View File
@@ -1,23 +1,22 @@
package manager package manager
import ( import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
) )
func (pm *PluginManager) GetPluginSettings(orgId int64) (map[string]*models.PluginSettingInfoDTO, error) { func (pm *PluginManager) GetPluginSettings(orgID int64) (map[string]*models.PluginSettingInfoDTO, error) {
query := models.GetPluginSettingsQuery{OrgId: orgId} pluginSettings, err := pm.SQLStore.GetPluginSettings(orgID)
if err := bus.Dispatch(&query); err != nil { if err != nil {
return nil, err return nil, err
} }
pluginMap := make(map[string]*models.PluginSettingInfoDTO) pluginMap := make(map[string]*models.PluginSettingInfoDTO)
for _, plug := range query.Result { for _, plug := range pluginSettings {
pluginMap[plug.PluginId] = plug pluginMap[plug.PluginId] = plug
} }
for _, pluginDef := range Plugins { for _, pluginDef := range pm.plugins {
// ignore entries that exists // ignore entries that exists
if _, ok := pluginMap[pluginDef.Id]; ok { if _, ok := pluginMap[pluginDef.Id]; ok {
continue continue
@@ -26,7 +25,7 @@ func (pm *PluginManager) GetPluginSettings(orgId int64) (map[string]*models.Plug
// default to enabled true // default to enabled true
opt := &models.PluginSettingInfoDTO{ opt := &models.PluginSettingInfoDTO{
PluginId: pluginDef.Id, PluginId: pluginDef.Id,
OrgId: orgId, OrgId: orgID,
Enabled: true, Enabled: true,
} }
@@ -72,7 +71,7 @@ func (pm *PluginManager) GetEnabledPlugins(orgID int64) (*plugins.EnabledPlugins
} }
// add all plugins that are not part of an App. // add all plugins that are not part of an App.
for dsID, ds := range DataSources { for dsID, ds := range pm.dataSources {
if _, exists := pluginSettingMap[ds.Id]; exists { if _, exists := pluginSettingMap[ds.Id]; exists {
enabledPlugins.DataSources[dsID] = ds enabledPlugins.DataSources[dsID] = ds
} }
+15 -15
View File
@@ -16,19 +16,19 @@ var (
httpClient = http.Client{Timeout: 10 * time.Second} httpClient = http.Client{Timeout: 10 * time.Second}
) )
type GrafanaNetPlugin struct { type grafanaNetPlugin struct {
Slug string `json:"slug"` Slug string `json:"slug"`
Version string `json:"version"` Version string `json:"version"`
} }
type GithubLatest struct { type gitHubLatest struct {
Stable string `json:"stable"` Stable string `json:"stable"`
Testing string `json:"testing"` Testing string `json:"testing"`
} }
func getAllExternalPluginSlugs() string { func (pm *PluginManager) getAllExternalPluginSlugs() string {
var result []string var result []string
for _, plug := range Plugins { for _, plug := range pm.plugins {
if plug.IsCorePlugin { if plug.IsCorePlugin {
continue continue
} }
@@ -46,7 +46,7 @@ func (pm *PluginManager) checkForUpdates() {
pm.log.Debug("Checking for updates") pm.log.Debug("Checking for updates")
pluginSlugs := getAllExternalPluginSlugs() pluginSlugs := pm.getAllExternalPluginSlugs()
resp, err := httpClient.Get("https://grafana.com/api/plugins/versioncheck?slugIn=" + pluginSlugs + "&grafanaVersion=" + setting.BuildVersion) resp, err := httpClient.Get("https://grafana.com/api/plugins/versioncheck?slugIn=" + pluginSlugs + "&grafanaVersion=" + setting.BuildVersion)
if err != nil { if err != nil {
log.Tracef("Failed to get plugins repo from grafana.com, %v", err.Error()) log.Tracef("Failed to get plugins repo from grafana.com, %v", err.Error())
@@ -64,14 +64,14 @@ func (pm *PluginManager) checkForUpdates() {
return return
} }
gNetPlugins := []GrafanaNetPlugin{} gNetPlugins := []grafanaNetPlugin{}
err = json.Unmarshal(body, &gNetPlugins) err = json.Unmarshal(body, &gNetPlugins)
if err != nil { if err != nil {
log.Tracef("Failed to unmarshal plugin repo, reading response from grafana.com, %v", err.Error()) log.Tracef("Failed to unmarshal plugin repo, reading response from grafana.com, %v", err.Error())
return return
} }
for _, plug := range Plugins { for _, plug := range pm.plugins {
for _, gplug := range gNetPlugins { for _, gplug := range gNetPlugins {
if gplug.Slug == plug.Id { if gplug.Slug == plug.Id {
plug.GrafanaNetVersion = gplug.Version plug.GrafanaNetVersion = gplug.Version
@@ -104,24 +104,24 @@ func (pm *PluginManager) checkForUpdates() {
return return
} }
var githubLatest GithubLatest var latest gitHubLatest
err = json.Unmarshal(body, &githubLatest) err = json.Unmarshal(body, &latest)
if err != nil { if err != nil {
log.Tracef("Failed to unmarshal github.com latest, reading response from github.com: %v", err.Error()) log.Tracef("Failed to unmarshal github.com latest, reading response from github.com: %v", err.Error())
return return
} }
if strings.Contains(setting.BuildVersion, "-") { if strings.Contains(setting.BuildVersion, "-") {
pm.GrafanaLatestVersion = githubLatest.Testing pm.grafanaLatestVersion = latest.Testing
pm.GrafanaHasUpdate = !strings.HasPrefix(setting.BuildVersion, githubLatest.Testing) pm.grafanaHasUpdate = !strings.HasPrefix(setting.BuildVersion, latest.Testing)
} else { } else {
pm.GrafanaLatestVersion = githubLatest.Stable pm.grafanaLatestVersion = latest.Stable
pm.GrafanaHasUpdate = githubLatest.Stable != setting.BuildVersion pm.grafanaHasUpdate = latest.Stable != setting.BuildVersion
} }
currVersion, err1 := version.NewVersion(setting.BuildVersion) currVersion, err1 := version.NewVersion(setting.BuildVersion)
latestVersion, err2 := version.NewVersion(pm.GrafanaLatestVersion) latestVersion, err2 := version.NewVersion(pm.grafanaLatestVersion)
if err1 == nil && err2 == nil { if err1 == nil && err2 == nil {
pm.GrafanaHasUpdate = currVersion.LessThan(latestVersion) pm.grafanaHasUpdate = currVersion.LessThan(latestVersion)
} }
} }
+7 -5
View File
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager" "github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/tsdb" "github.com/grafana/grafana/pkg/tsdb"
) )
@@ -22,6 +23,7 @@ func init() {
type Service struct { type Service struct {
DataService *tsdb.Service `inject:""` DataService *tsdb.Service `inject:""`
PluginManager *manager.PluginManager `inject:""` PluginManager *manager.PluginManager `inject:""`
SQLStore *sqlstore.SQLStore `inject:""`
logger log.Logger logger log.Logger
} }
@@ -40,19 +42,19 @@ func (s *Service) Run(ctx context.Context) error {
func (s *Service) updateAppDashboards() { func (s *Service) updateAppDashboards() {
s.logger.Debug("Looking for app dashboard updates") s.logger.Debug("Looking for app dashboard updates")
query := models.GetPluginSettingsQuery{OrgId: 0} pluginSettings, err := s.SQLStore.GetPluginSettings(0)
if err := bus.Dispatch(&query); err != nil { if err != nil {
s.logger.Error("Failed to get all plugin settings", "error", err) s.logger.Error("Failed to get all plugin settings", "error", err)
return return
} }
for _, pluginSetting := range query.Result { for _, pluginSetting := range pluginSettings {
// ignore disabled plugins // ignore disabled plugins
if !pluginSetting.Enabled { if !pluginSetting.Enabled {
continue continue
} }
if pluginDef, exists := manager.Plugins[pluginSetting.PluginId]; exists { if pluginDef := s.PluginManager.GetPlugin(pluginSetting.PluginId); pluginDef != nil {
if pluginDef.Info.Version != pluginSetting.PluginVersion { if pluginDef.Info.Version != pluginSetting.PluginVersion {
s.syncPluginDashboards(pluginDef, pluginSetting.OrgId) s.syncPluginDashboards(pluginDef, pluginSetting.OrgId)
} }
@@ -117,7 +119,7 @@ func (s *Service) handlePluginStateChanged(event *models.PluginStateChangedEvent
s.logger.Info("Plugin state changed", "pluginId", event.PluginId, "enabled", event.Enabled) s.logger.Info("Plugin state changed", "pluginId", event.PluginId, "enabled", event.Enabled)
if event.Enabled { if event.Enabled {
s.syncPluginDashboards(manager.Plugins[event.PluginId], event.OrgId) s.syncPluginDashboards(s.PluginManager.GetPlugin(event.PluginId), event.OrgId)
} else { } else {
query := models.GetDashboardsByPluginIdQuery{PluginId: event.PluginId, OrgId: event.OrgId} query := models.GetDashboardsByPluginIdQuery{PluginId: event.PluginId, OrgId: event.OrgId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
-36
View File
@@ -1,36 +0,0 @@
package alerting
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("alerting", updateDashboardAlerts)
bus.AddHandler("alerting", validateDashboardAlerts)
}
func validateDashboardAlerts(cmd *models.ValidateDashboardAlertsCommand) error {
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId, cmd.User)
return extractor.ValidateAlerts()
}
func updateDashboardAlerts(cmd *models.UpdateDashboardAlertsCommand) error {
saveAlerts := models.SaveAlertsCommand{
OrgId: cmd.OrgId,
UserId: cmd.User.UserId,
DashboardId: cmd.Dashboard.Id,
}
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId, cmd.User)
alerts, err := extractor.GetAlerts()
if err != nil {
return err
}
saveAlerts.Alerts = alerts
return bus.Dispatch(&saveAlerts)
}
+2 -2
View File
@@ -10,10 +10,10 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"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/plugins"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/ext"
tlog "github.com/opentracing/opentracing-go/log" tlog "github.com/opentracing/opentracing-go/log"
@@ -27,7 +27,7 @@ type AlertEngine struct {
RenderService rendering.Service `inject:""` RenderService rendering.Service `inject:""`
Bus bus.Bus `inject:""` Bus bus.Bus `inject:""`
RequestValidator models.PluginRequestValidator `inject:""` RequestValidator models.PluginRequestValidator `inject:""`
DataService *tsdb.Service `inject:""` DataService plugins.DataRequestHandler `inject:""`
execQueue chan *Job execQueue chan *Job
ticker *Ticker ticker *Ticker
+4 -2
View File
@@ -61,7 +61,7 @@ func findPanelQueryByRefID(panel *simplejson.Json, refID string) *simplejson.Jso
func copyJSON(in json.Marshaler) (*simplejson.Json, error) { func copyJSON(in json.Marshaler) (*simplejson.Json, error) {
rawJSON, err := in.MarshalJSON() rawJSON, err := in.MarshalJSON()
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("JSON marshaling failed: %w", err)
} }
return simplejson.NewJson(rawJSON) return simplejson.NewJson(rawJSON)
@@ -242,6 +242,8 @@ func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *models.Alert
// 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() error { func (e *DashAlertExtractor) ValidateAlerts() error {
_, err := e.extractAlerts(func(alert *models.Alert) bool { return alert.OrgId != 0 && alert.PanelId != 0 }, false) _, err := e.extractAlerts(func(alert *models.Alert) bool {
return alert.OrgId != 0 && alert.PanelId != 0
}, false)
return err return err
} }
+2 -8
View File
@@ -3,11 +3,10 @@ package dashboards
import ( import (
"time" "time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
) )
func MakeUserAdmin(bus bus.Bus, orgID int64, userID int64, dashboardID int64, setViewAndEditPermissions bool) error { func (dr *dashboardServiceImpl) MakeUserAdmin(orgID int64, userID int64, dashboardID int64, setViewAndEditPermissions bool) error {
rtEditor := models.ROLE_EDITOR rtEditor := models.ROLE_EDITOR
rtViewer := models.ROLE_VIEWER rtViewer := models.ROLE_VIEWER
@@ -43,12 +42,7 @@ func MakeUserAdmin(bus bus.Bus, orgID int64, userID int64, dashboardID int64, se
) )
} }
aclCmd := &models.UpdateDashboardAclCommand{ if err := dr.dashboardStore.UpdateDashboardACL(dashboardID, items); err != nil {
DashboardID: dashboardID,
Items: items,
}
if err := bus.Dispatch(aclCmd); err != nil {
return err return err
} }
+75 -74
View File
@@ -1,10 +1,13 @@
package dashboards package dashboards
import ( import (
"fmt"
"strings" "strings"
"time" "time"
"github.com/grafana/grafana/pkg/components/gtime" "github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
@@ -20,6 +23,7 @@ type DashboardService interface {
SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error)
DeleteDashboard(dashboardId int64, orgId int64) error DeleteDashboard(dashboardId int64, orgId int64) error
MakeUserAdmin(orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error
} }
// DashboardProvisioningService is a service for operating on provisioned dashboards. // DashboardProvisioningService is a service for operating on provisioned dashboards.
@@ -27,21 +31,22 @@ type DashboardProvisioningService interface {
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error) SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardID(DashboardId int64) (*models.DashboardProvisioning, error) GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
UnprovisionDashboard(dashboardId int64) error UnprovisionDashboard(dashboardID int64) error
DeleteProvisionedDashboard(dashboardId int64, orgId int64) error DeleteProvisionedDashboard(dashboardID int64, orgID int64) error
} }
// NewService is a factory for creating a new dashboard service. // NewService is a factory for creating a new dashboard service.
var NewService = func() DashboardService { var NewService = func(store dashboards.Store) DashboardService {
return &dashboardServiceImpl{ return &dashboardServiceImpl{
log: log.New("dashboard-service"), dashboardStore: store,
log: log.New("dashboard-service"),
} }
} }
// NewProvisioningService is a factory for creating a new dashboard provisioning service. // NewProvisioningService is a factory for creating a new dashboard provisioning service.
var NewProvisioningService = func() DashboardProvisioningService { var NewProvisioningService = func(store dashboards.Store) DashboardProvisioningService {
return NewService().(*dashboardServiceImpl) return NewService(store).(*dashboardServiceImpl)
} }
type SaveDashboardDTO struct { type SaveDashboardDTO struct {
@@ -54,35 +59,32 @@ type SaveDashboardDTO struct {
} }
type dashboardServiceImpl struct { type dashboardServiceImpl struct {
orgId int64 dashboardStore dashboards.Store
user *models.SignedInUser orgId int64
log log.Logger user *models.SignedInUser
log log.Logger
} }
func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) { func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
cmd := &models.GetProvisionedDashboardDataQuery{Name: name} return dr.dashboardStore.GetProvisionedDashboardData(name)
err := bus.Dispatch(cmd)
if err != nil {
return nil, err
}
return cmd.Result, nil
} }
func (dr *dashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(dashboardId int64) (*models.DashboardProvisioning, error) { // GetProvisionedData gets provisioned dashboard data.
cmd := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: dashboardId} //
err := bus.Dispatch(cmd) // Stubbable by tests.
if err != nil { var GetProvisionedData = func(store dashboards.Store, dashboardID int64) (*models.DashboardProvisioning, error) {
return nil, err return store.GetProvisionedDataByDashboardID(dashboardID)
}
return cmd.Result, nil
} }
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, validateAlerts bool, func (dr *dashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
return GetProvisionedData(dr.dashboardStore, dashboardID)
}
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, shouldValidateAlerts bool,
validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) { validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) {
dash := dto.Dashboard dash := dto.Dashboard
dash.OrgId = dto.OrgId
dash.Title = strings.TrimSpace(dash.Title) dash.Title = strings.TrimSpace(dash.Title)
dash.Data.Set("title", dash.Title) dash.Data.Set("title", dash.Title)
dash.SetUid(strings.TrimSpace(dash.Uid)) dash.SetUid(strings.TrimSpace(dash.Uid))
@@ -109,29 +111,18 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
return nil, err return nil, err
} }
if validateAlerts { if shouldValidateAlerts {
validateAlertsCmd := models.ValidateDashboardAlertsCommand{ if err := validateAlerts(dash, dto.User); err != nil {
OrgId: dto.OrgId,
Dashboard: dash,
User: dto.User,
}
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
return nil, err return nil, err
} }
} }
validateBeforeSaveCmd := models.ValidateDashboardBeforeSaveCommand{ isParentFolderChanged, err := dr.dashboardStore.ValidateDashboardBeforeSave(dash, dto.Overwrite)
OrgId: dto.OrgId, if err != nil {
Dashboard: dash,
Overwrite: dto.Overwrite,
}
if err := bus.Dispatch(&validateBeforeSaveCmd); err != nil {
return nil, err return nil, err
} }
if validateBeforeSaveCmd.Result.IsParentFolderChanged { if isParentFolderChanged {
folderGuardian := guardian.New(dash.FolderId, dto.OrgId, dto.User) folderGuardian := guardian.New(dash.FolderId, dto.OrgId, dto.User)
if canSave, err := folderGuardian.CanSave(); err != nil || !canSave { if canSave, err := folderGuardian.CanSave(); err != nil || !canSave {
if err != nil { if err != nil {
@@ -178,6 +169,11 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
return cmd, nil return cmd, nil
} }
var validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
extractor := alerting.NewDashAlertExtractor(dash, dash.OrgId, user)
return extractor.ValidateAlerts()
}
func validateDashboardRefreshInterval(dash *models.Dashboard) error { func validateDashboardRefreshInterval(dash *models.Dashboard) error {
if setting.MinRefreshInterval == "" { if setting.MinRefreshInterval == "" {
return nil return nil
@@ -191,11 +187,11 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error {
minRefreshInterval, err := gtime.ParseDuration(setting.MinRefreshInterval) minRefreshInterval, err := gtime.ParseDuration(setting.MinRefreshInterval)
if err != nil { if err != nil {
return err return fmt.Errorf("parsing min refresh interval %q failed: %w", setting.MinRefreshInterval, err)
} }
d, err := gtime.ParseDuration(refresh) d, err := gtime.ParseDuration(refresh)
if err != nil { if err != nil {
return err return fmt.Errorf("parsing refresh duration %q failed: %w", refresh, err)
} }
if d < minRefreshInterval { if d < minRefreshInterval {
@@ -205,14 +201,17 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error {
return nil return nil
} }
func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error { // UpdateAlerting updates alerting.
alertCmd := models.UpdateDashboardAlertsCommand{ //
OrgId: dto.OrgId, // Stubbable by tests.
Dashboard: cmd.Result, var UpdateAlerting = func(store dashboards.Store, orgID int64, dashboard *models.Dashboard, user *models.SignedInUser) error {
User: dto.User, extractor := alerting.NewDashAlertExtractor(dashboard, orgID, user)
alerts, err := extractor.GetAlerts()
if err != nil {
return err
} }
return bus.Dispatch(&alertCmd) return store.SaveAlerts(dashboard.Id, alerts)
} }
func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO, func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO,
@@ -234,24 +233,18 @@ func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO,
return nil, err return nil, err
} }
saveCmd := &models.SaveProvisionedDashboardCommand{
DashboardCmd: cmd,
DashboardProvisioning: provisioning,
}
// dashboard // dashboard
err = bus.Dispatch(saveCmd) dash, err := dr.dashboardStore.SaveProvisionedDashboard(*cmd, provisioning)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// alerts // alerts
err = dr.updateAlerting(cmd, dto) if err := UpdateAlerting(dr.dashboardStore, dto.OrgId, dash, dto.User); err != nil {
if err != nil {
return nil, err return nil, err
} }
return cmd.Result, nil return dash, nil
} }
func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDashboardDTO) (*models.Dashboard, error) { func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDashboardDTO) (*models.Dashboard, error) {
@@ -264,22 +257,23 @@ func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDash
return nil, err return nil, err
} }
err = bus.Dispatch(cmd) dash, err := dr.dashboardStore.SaveDashboard(*cmd)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = dr.updateAlerting(cmd, dto) if err := UpdateAlerting(dr.dashboardStore, dto.OrgId, dash, dto.User); err != nil {
if err != nil {
return nil, err return nil, err
} }
return cmd.Result, nil return dash, nil
} }
func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) { func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) {
if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil { if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil {
dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval", "dashboardUid", dto.Dashboard.Uid, "dashboardTitle", dto.Dashboard.Title, "minRefreshInterval", setting.MinRefreshInterval) dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval",
"dashboardUid", dto.Dashboard.Uid, "dashboardTitle", dto.Dashboard.Title, "minRefreshInterval",
setting.MinRefreshInterval)
dto.Dashboard.Data.Set("refresh", setting.MinRefreshInterval) dto.Dashboard.Data.Set("refresh", setting.MinRefreshInterval)
} }
@@ -288,17 +282,16 @@ func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO, allowUiUpda
return nil, err return nil, err
} }
err = bus.Dispatch(cmd) dash, err := dr.dashboardStore.SaveDashboard(*cmd)
if err != nil { if err != nil {
return nil, fmt.Errorf("saving dashboard failed: %w", err)
}
if err := UpdateAlerting(dr.dashboardStore, dto.OrgId, dash, dto.User); err != nil {
return nil, err return nil, err
} }
err = dr.updateAlerting(cmd, dto) return dash, nil
if err != nil {
return nil, err
}
return cmd.Result, nil
} }
// DeleteDashboard removes dashboard from the DB. Errors out if the dashboard was provisioned. Should be used for // DeleteDashboard removes dashboard from the DB. Errors out if the dashboard was provisioned. Should be used for
@@ -327,9 +320,12 @@ func (dr *dashboardServiceImpl) deleteDashboard(dashboardId int64, orgId int64,
return bus.Dispatch(cmd) return bus.Dispatch(cmd)
} }
func (dr *dashboardServiceImpl) ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) { func (dr *dashboardServiceImpl) ImportDashboard(dto *SaveDashboardDTO) (
*models.Dashboard, error) {
if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil { if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil {
dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval", "dashboardUid", dto.Dashboard.Uid, "dashboardTitle", dto.Dashboard.Title, "minRefreshInterval", setting.MinRefreshInterval) dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval",
"dashboardUid", dto.Dashboard.Uid, "dashboardTitle", dto.Dashboard.Title,
"minRefreshInterval", setting.MinRefreshInterval)
dto.Dashboard.Data.Set("refresh", setting.MinRefreshInterval) dto.Dashboard.Data.Set("refresh", setting.MinRefreshInterval)
} }
@@ -359,6 +355,7 @@ type FakeDashboardService struct {
SaveDashboardResult *models.Dashboard SaveDashboardResult *models.Dashboard
SaveDashboardError error SaveDashboardError error
SavedDashboards []*SaveDashboardDTO SavedDashboards []*SaveDashboardDTO
ProvisionedDashData *models.DashboardProvisioning
} }
func (s *FakeDashboardService) SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) { func (s *FakeDashboardService) SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) {
@@ -385,8 +382,12 @@ func (s *FakeDashboardService) DeleteDashboard(dashboardId int64, orgId int64) e
return nil return nil
} }
func (s *FakeDashboardService) GetProvisionedDashboardDataByDashboardID(id int64) (*models.DashboardProvisioning, error) {
return s.ProvisionedDashData, nil
}
func MockDashboardService(mock *FakeDashboardService) { func MockDashboardService(mock *FakeDashboardService) {
NewService = func() DashboardService { NewService = func(dashboards.Store) DashboardService {
return mock return mock
} }
} }
@@ -0,0 +1,928 @@
// +build integration
package dashboards
import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
)
const testOrgID int64 = 1
func TestIntegratedDashboardService(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(store dashboards.Store, orgID int64, dashboard *models.Dashboard, user *models.SignedInUser) error {
return nil
}
// Basic validation tests
permissionScenario(t, "When saving a dashboard with non-existing id", true,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": float64(123412321),
"title": "Expect error",
}),
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardNotFound, err)
})
// Given other organization
t.Run("Given organization B", func(t *testing.T) {
const otherOrgId int64 = 2
permissionScenario(t, "When creating a dashboard with same id as dashboard in organization A",
true, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: otherOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInFolder.Id,
"title": "Expect error",
}),
Overwrite: false,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardNotFound, err)
})
permissionScenario(t, "When creating a dashboard with same uid as dashboard in organization A, it should create a new dashboard in org B",
true, func(t *testing.T, sc *permissionScenarioContext) {
const otherOrgId int64 = 2
cmd := models.SaveDashboardCommand{
OrgId: otherOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": sc.savedDashInFolder.Uid,
"title": "Dash with existing uid in other org",
}),
Overwrite: false,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
dash, err := sc.sqlStore.GetDashboard(0, otherOrgId, sc.savedDashInFolder.Uid, "")
require.NoError(t, err)
assert.NotEqual(t, sc.savedDashInFolder.Id, dash.Id)
assert.Equal(t, res.Id, dash.Id)
assert.Equal(t, otherOrgId, dash.OrgId)
assert.Equal(t, sc.savedDashInFolder.Uid, dash.Uid)
})
})
t.Run("Given user has no permission to save", func(t *testing.T) {
const canSave = false
permissionScenario(t, "When creating a new dashboard in the General folder", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
sqlStore := sqlstore.InitTestDB(t)
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "Dash",
}),
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sqlStore)
assert.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, int64(0), sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
permissionScenario(t, "When creating a new dashboard in other folder, it should create dashboard guardian for other folder with correct arguments and rsult in access denied error",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "Dash",
}),
FolderId: sc.otherSavedFolder.Id,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.otherSavedFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
permissionScenario(t, "When creating a new dashboard by existing title in folder, it should create dashboard guardian for folder with correct arguments and result in access denied error",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": sc.savedDashInFolder.Title,
}),
FolderId: sc.savedFolder.Id,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
permissionScenario(t, "When creating a new dashboard by existing UID in folder, it should create dashboard guardian for folder with correct arguments and result in access denied error",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": sc.savedDashInFolder.Uid,
"title": "New dash",
}),
FolderId: sc.savedFolder.Id,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
permissionScenario(t, "When updating a dashboard by existing id in the General folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInGeneralFolder.Id,
"title": "Dash",
}),
FolderId: sc.savedDashInGeneralFolder.FolderId,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
permissionScenario(t, "When updating a dashboard by existing id in other folder, it should create dashboard guardian for dashboard with correct arguments and result in access denied error",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInFolder.Id,
"title": "Dash",
}),
FolderId: sc.savedDashInFolder.FolderId,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
permissionScenario(t, "When moving a dashboard by existing ID to other folder from General folder, it should create dashboard guardian for other folder with correct arguments and result in access denied error",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInGeneralFolder.Id,
"title": "Dash",
}),
FolderId: sc.otherSavedFolder.Id,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.otherSavedFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
permissionScenario(t, "When moving a dashboard by existing id to the General folder from other folder, it should create dashboard guardian for General folder with correct arguments and result in access denied error",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInFolder.Id,
"title": "Dash",
}),
FolderId: 0,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, int64(0), sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
permissionScenario(t, "When moving a dashboard by existing uid to other folder from General folder, it should create dashboard guardian for other folder with correct arguments and result in access denied error",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": sc.savedDashInGeneralFolder.Uid,
"title": "Dash",
}),
FolderId: sc.otherSavedFolder.Id,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.otherSavedFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
permissionScenario(t, "When moving a dashboard by existing UID to the General folder from other folder, it should create dashboard guardian for General folder with correct arguments and result in access denied error",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": sc.savedDashInFolder.Uid,
"title": "Dash",
}),
FolderId: 0,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, int64(0), sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
assert.Equal(t, cmd.UserId, sc.dashboardGuardianMock.User.UserId)
})
})
t.Run("Given user has permission to save", func(t *testing.T) {
const canSave = true
t.Run("and overwrite flag is set to false", func(t *testing.T) {
const shouldOverwrite = false
permissionScenario(t, "When creating a dashboard in General folder with same name as dashboard in other folder",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": sc.savedDashInFolder.Title,
}),
FolderId: 0,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
dash, err := sc.sqlStore.GetDashboard(res.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, res.Id, dash.Id)
assert.Equal(t, int64(0), dash.FolderId)
})
permissionScenario(t, "When creating a dashboard in other folder with same name as dashboard in General folder",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": sc.savedDashInGeneralFolder.Title,
}),
FolderId: sc.savedFolder.Id,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
assert.NotEqual(t, sc.savedDashInGeneralFolder.Id, res.Id)
dash, err := sc.sqlStore.GetDashboard(res.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, sc.savedFolder.Id, dash.FolderId)
})
permissionScenario(t, "When creating a folder with same name as dashboard in other folder",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": sc.savedDashInFolder.Title,
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
assert.NotEqual(t, sc.savedDashInGeneralFolder.Id, res.Id)
assert.True(t, res.IsFolder)
dash, err := sc.sqlStore.GetDashboard(res.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, int64(0), dash.FolderId)
assert.True(t, dash.IsFolder)
})
permissionScenario(t, "When saving a dashboard without id and uid and unique title in folder",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "Dash without id and uid",
}),
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
assert.Greater(t, res.Id, int64(0))
assert.NotEmpty(t, res.Uid)
dash, err := sc.sqlStore.GetDashboard(res.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, res.Id, dash.Id)
assert.Equal(t, res.Uid, dash.Uid)
})
permissionScenario(t, "When saving a dashboard when dashboard id is zero ", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": 0,
"title": "Dash with zero id",
}),
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
dash, err := sc.sqlStore.GetDashboard(res.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, res.Id, dash.Id)
})
permissionScenario(t, "When saving a dashboard in non-existing folder", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "Expect error",
}),
FolderId: 123412321,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardFolderNotFound, err)
})
permissionScenario(t, "When updating an existing dashboard by id without current version", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInGeneralFolder.Id,
"title": "test dash 23",
}),
FolderId: sc.savedFolder.Id,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardVersionMismatch, err)
})
permissionScenario(t, "When updating an existing dashboard by id with current version", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInGeneralFolder.Id,
"title": "Updated title",
"version": sc.savedDashInGeneralFolder.Version,
}),
FolderId: sc.savedFolder.Id,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
dash, err := sc.sqlStore.GetDashboard(sc.savedDashInGeneralFolder.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, "Updated title", dash.Title)
assert.Equal(t, sc.savedFolder.Id, dash.FolderId)
assert.Greater(t, dash.Version, sc.savedDashInGeneralFolder.Version)
})
permissionScenario(t, "When updating an existing dashboard by uid without current version", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": sc.savedDashInFolder.Uid,
"title": "test dash 23",
}),
FolderId: 0,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardVersionMismatch, err)
})
permissionScenario(t, "When updating an existing dashboard by uid with current version", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": sc.savedDashInFolder.Uid,
"title": "Updated title",
"version": sc.savedDashInFolder.Version,
}),
FolderId: 0,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
dash, err := sc.sqlStore.GetDashboard(sc.savedDashInFolder.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, "Updated title", dash.Title)
assert.Equal(t, int64(0), dash.FolderId)
assert.Greater(t, dash.Version, sc.savedDashInFolder.Version)
})
permissionScenario(t, "When creating a dashboard with same name as dashboard in other folder",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": sc.savedDashInFolder.Title,
}),
FolderId: sc.savedDashInFolder.FolderId,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameNameInFolderExists, err)
})
permissionScenario(t, "When creating a dashboard with same name as dashboard in General folder",
canSave, func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": sc.savedDashInGeneralFolder.Title,
}),
FolderId: sc.savedDashInGeneralFolder.FolderId,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameNameInFolderExists, err)
})
permissionScenario(t, "When creating a folder with same name as existing folder", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": sc.savedFolder.Title,
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameNameInFolderExists, err)
})
})
t.Run("and overwrite flag is set to true", func(t *testing.T) {
const shouldOverwrite = true
permissionScenario(t, "When updating an existing dashboard by id without current version", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInGeneralFolder.Id,
"title": "Updated title",
}),
FolderId: sc.savedFolder.Id,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
dash, err := sc.sqlStore.GetDashboard(sc.savedDashInGeneralFolder.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, "Updated title", dash.Title)
assert.Equal(t, sc.savedFolder.Id, dash.FolderId)
assert.Greater(t, dash.Version, sc.savedDashInGeneralFolder.Version)
})
permissionScenario(t, "When updating an existing dashboard by uid without current version", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": sc.savedDashInFolder.Uid,
"title": "Updated title",
}),
FolderId: 0,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
dash, err := sc.sqlStore.GetDashboard(sc.savedDashInFolder.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, "Updated title", dash.Title)
assert.Equal(t, int64(0), dash.FolderId)
assert.Greater(t, dash.Version, sc.savedDashInFolder.Version)
})
permissionScenario(t, "When updating uid for existing dashboard using id", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInFolder.Id,
"uid": "new-uid",
"title": sc.savedDashInFolder.Title,
}),
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
assert.Equal(t, sc.savedDashInFolder.Id, res.Id)
assert.Equal(t, "new-uid", res.Uid)
dash, err := sc.sqlStore.GetDashboard(sc.savedDashInFolder.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, "new-uid", dash.Uid)
assert.Greater(t, dash.Version, sc.savedDashInFolder.Version)
})
permissionScenario(t, "When updating uid to an existing uid for existing dashboard using id", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInFolder.Id,
"uid": sc.savedDashInGeneralFolder.Uid,
"title": sc.savedDashInFolder.Title,
}),
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameUIDExists, err)
})
permissionScenario(t, "When creating a dashboard with same name as dashboard in other folder", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": sc.savedDashInFolder.Title,
}),
FolderId: sc.savedDashInFolder.FolderId,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
assert.Equal(t, sc.savedDashInFolder.Id, res.Id)
assert.Equal(t, sc.savedDashInFolder.Uid, res.Uid)
dash, err := sc.sqlStore.GetDashboard(res.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, res.Id, dash.Id)
assert.Equal(t, res.Uid, dash.Uid)
})
permissionScenario(t, "When creating a dashboard with same name as dashboard in General folder", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: testOrgID,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": sc.savedDashInGeneralFolder.Title,
}),
FolderId: sc.savedDashInGeneralFolder.FolderId,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(t, cmd, sc.sqlStore)
require.NotNil(t, res)
assert.Equal(t, sc.savedDashInGeneralFolder.Id, res.Id)
assert.Equal(t, sc.savedDashInGeneralFolder.Uid, res.Uid)
dash, err := sc.sqlStore.GetDashboard(res.Id, cmd.OrgId, "", "")
require.NoError(t, err)
assert.Equal(t, res.Id, dash.Id)
assert.Equal(t, res.Uid, dash.Uid)
})
permissionScenario(t, "When updating existing folder to a dashboard using id", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedFolder.Id,
"title": "new title",
}),
IsFolder: false,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardTypeMismatch, err)
})
permissionScenario(t, "When updating existing dashboard to a folder using id", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": sc.savedDashInFolder.Id,
"title": "new folder title",
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardTypeMismatch, err)
})
permissionScenario(t, "When updating existing folder to a dashboard using uid", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": sc.savedFolder.Uid,
"title": "new title",
}),
IsFolder: false,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardTypeMismatch, err)
})
permissionScenario(t, "When updating existing dashboard to a folder using uid", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": sc.savedDashInFolder.Uid,
"title": "new folder title",
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardTypeMismatch, err)
})
permissionScenario(t, "When updating existing folder to a dashboard using title", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": sc.savedFolder.Title,
}),
IsFolder: false,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameNameAsFolder, err)
})
permissionScenario(t, "When updating existing dashboard to a folder using title", canSave,
func(t *testing.T, sc *permissionScenarioContext) {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": sc.savedDashInGeneralFolder.Title,
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardFolderWithSameNameAsDashboard, err)
})
})
})
})
}
type permissionScenarioContext struct {
dashboardGuardianMock *guardian.FakeDashboardGuardian
sqlStore *sqlstore.SQLStore
savedFolder *models.Dashboard
savedDashInFolder *models.Dashboard
otherSavedFolder *models.Dashboard
savedDashInGeneralFolder *models.Dashboard
}
type permissionScenarioFunc func(t *testing.T, sc *permissionScenarioContext)
func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionScenarioFunc) {
t.Helper()
mock := &guardian.FakeDashboardGuardian{
CanSaveValue: canSave,
}
t.Run(desc, func(t *testing.T) {
sqlStore := sqlstore.InitTestDB(t)
savedFolder := saveTestFolder(t, "Saved folder", testOrgID, sqlStore)
savedDashInFolder := saveTestDashboard(t, "Saved dash in folder", testOrgID, savedFolder.Id, sqlStore)
saveTestDashboard(t, "Other saved dash in folder", testOrgID, savedFolder.Id, sqlStore)
savedDashInGeneralFolder := saveTestDashboard(t, "Saved dashboard in general folder", testOrgID, 0, sqlStore)
otherSavedFolder := saveTestFolder(t, "Other saved folder", testOrgID, sqlStore)
require.Equal(t, "Saved folder", savedFolder.Title)
require.Equal(t, "saved-folder", savedFolder.Slug)
require.NotEqual(t, int64(0), savedFolder.Id)
require.True(t, savedFolder.IsFolder)
require.Equal(t, int64(0), savedFolder.FolderId)
require.NotEmpty(t, savedFolder.Uid)
require.Equal(t, "Saved dash in folder", savedDashInFolder.Title)
require.Equal(t, "saved-dash-in-folder", savedDashInFolder.Slug)
require.NotEqual(t, int64(0), savedDashInFolder.Id)
require.False(t, savedDashInFolder.IsFolder)
require.Equal(t, savedFolder.Id, savedDashInFolder.FolderId)
require.NotEmpty(t, savedDashInFolder.Uid)
origNewDashboardGuardian := guardian.New
t.Cleanup(func() {
guardian.New = origNewDashboardGuardian
})
guardian.MockDashboardGuardian(mock)
sc := &permissionScenarioContext{
dashboardGuardianMock: mock,
sqlStore: sqlStore,
savedDashInFolder: savedDashInFolder,
otherSavedFolder: otherSavedFolder,
savedDashInGeneralFolder: savedDashInGeneralFolder,
savedFolder: savedFolder,
}
fn(t, sc)
})
}
func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore *sqlstore.SQLStore) *models.Dashboard {
t.Helper()
dto := toSaveDashboardDto(cmd)
res, err := NewService(sqlStore).SaveDashboard(&dto, false)
require.NoError(t, err)
return res
}
func callSaveWithError(cmd models.SaveDashboardCommand, sqlStore *sqlstore.SQLStore) error {
dto := toSaveDashboardDto(cmd)
_, err := NewService(sqlStore).SaveDashboard(&dto, false)
return err
}
func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlStore *sqlstore.SQLStore) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgID,
FolderId: folderID,
IsFolder: false,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
}),
}
dto := SaveDashboardDTO{
OrgId: orgID,
Dashboard: cmd.GetDashboardModel(),
User: &models.SignedInUser{
UserId: 1,
OrgRole: models.ROLE_ADMIN,
},
}
res, err := NewService(sqlStore).SaveDashboard(&dto, false)
require.NoError(t, err)
return res
}
func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore.SQLStore) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgID,
FolderId: 0,
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
}),
}
dto := SaveDashboardDTO{
OrgId: orgID,
Dashboard: cmd.GetDashboardModel(),
User: &models.SignedInUser{
UserId: 1,
OrgRole: models.ROLE_ADMIN,
},
}
res, err := NewService(sqlStore).SaveDashboard(&dto, false)
require.NoError(t, err)
return res
}
func toSaveDashboardDto(cmd models.SaveDashboardCommand) SaveDashboardDTO {
dash := (&cmd).GetDashboardModel()
return SaveDashboardDTO{
Dashboard: dash,
Message: cmd.Message,
OrgId: cmd.OrgId,
User: &models.SignedInUser{UserId: cmd.UserId},
Overwrite: cmd.Overwrite,
}
}
+108 -74
View File
@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@@ -17,8 +18,10 @@ func TestDashboardService(t *testing.T) {
Convey("Dashboard service tests", t, func() { Convey("Dashboard service tests", t, func() {
bus.ClearBusHandlers() bus.ClearBusHandlers()
fakeStore := fakeDashboardStore{}
service := &dashboardServiceImpl{ service := &dashboardServiceImpl{
log: log.New("test.logger"), log: log.New("test.logger"),
dashboardStore: &fakeStore,
} }
origNewDashboardGuardian := guardian.New origNewDashboardGuardian := guardian.New
@@ -51,19 +54,13 @@ func TestDashboardService(t *testing.T) {
}) })
Convey("When saving a dashboard should validate uid", func() { Convey("When saving a dashboard should validate uid", func() {
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origValidateAlerts := validateAlerts
return nil t.Cleanup(func() {
validateAlerts = origValidateAlerts
}) })
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return nil return nil
}) }
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})
testCases := []struct { testCases := []struct {
Uid string Uid string
@@ -89,27 +86,23 @@ func TestDashboardService(t *testing.T) {
}) })
Convey("Should return validation error if dashboard is provisioned", func() { Convey("Should return validation error if dashboard is provisioned", func() {
provisioningValidated := false t.Cleanup(func() {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error { fakeStore.provisionedData = nil
provisioningValidated = true
cmd.Result = &models.DashboardProvisioning{}
return nil
}) })
fakeStore.provisionedData = &models.DashboardProvisioning{}
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origValidateAlerts := validateAlerts
return nil t.Cleanup(func() {
validateAlerts = origValidateAlerts
}) })
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return nil return nil
}) }
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3) dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1} dto.User = &models.SignedInUser{UserId: 1}
_, err := service.SaveDashboard(dto, false) _, err := service.SaveDashboard(dto, false)
So(provisioningValidated, ShouldBeTrue)
So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard) So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard)
}) })
@@ -121,21 +114,20 @@ func TestDashboardService(t *testing.T) {
return nil return nil
}) })
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origValidateAlerts := validateAlerts
return nil t.Cleanup(func() {
validateAlerts = origValidateAlerts
}) })
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return nil return nil
}) }
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3) dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1} dto.User = &models.SignedInUser{UserId: 1}
_, err := service.SaveDashboard(dto, true) _, err := service.SaveDashboard(dto, true)
So(provisioningValidated, ShouldBeFalse) So(provisioningValidated, ShouldBeFalse)
So(err, ShouldNotBeNil) So(err, ShouldBeNil)
}) })
Convey("Should return validation error if alert data is invalid", func() { Convey("Should return validation error if alert data is invalid", func() {
@@ -144,9 +136,13 @@ func TestDashboardService(t *testing.T) {
return nil return nil
}) })
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origValidateAlerts := validateAlerts
return fmt.Errorf("alert validation error") t.Cleanup(func() {
validateAlerts = origValidateAlerts
}) })
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
return fmt.Errorf("alert validation error")
}
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
_, err := service.SaveDashboard(dto, false) _, err := service.SaveDashboard(dto, false)
@@ -165,23 +161,27 @@ func TestDashboardService(t *testing.T) {
return nil return nil
}) })
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origUpdateAlerting := UpdateAlerting
return nil t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
}) })
UpdateAlerting = func(store dashboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
}
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error { origValidateAlerts := validateAlerts
cmd.Result = &models.ValidateDashboardBeforeSaveResult{} t.Cleanup(func() {
return nil validateAlerts = origValidateAlerts
}) })
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
return nil
}
bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error { bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error {
return nil return nil
}) })
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
})
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3) dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1} dto.User = &models.SignedInUser{UserId: 1}
@@ -200,22 +200,26 @@ func TestDashboardService(t *testing.T) {
return nil return nil
}) })
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origValidateAlerts := validateAlerts
return nil t.Cleanup(func() {
validateAlerts = origValidateAlerts
}) })
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return nil return nil
}) }
bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error { bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error {
return nil return nil
}) })
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error { origUpdateAlerting := UpdateAlerting
return nil t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
}) })
UpdateAlerting = func(store dashboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
}
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3) dto.Dashboard.SetId(3)
@@ -231,41 +235,42 @@ func TestDashboardService(t *testing.T) {
dto := &SaveDashboardDTO{} dto := &SaveDashboardDTO{}
Convey("Should return validation error if dashboard is provisioned", func() { Convey("Should return validation error if dashboard is provisioned", func() {
provisioningValidated := false t.Cleanup(func() {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error { fakeStore.provisionedData = nil
provisioningValidated = true
cmd.Result = &models.DashboardProvisioning{}
return nil
}) })
fakeStore.provisionedData = &models.DashboardProvisioning{}
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origValidateAlerts := validateAlerts
return nil t.Cleanup(func() {
validateAlerts = origValidateAlerts
}) })
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return nil return nil
}) }
bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error { bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error {
return nil return nil
}) })
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error { origUpdateAlerting := UpdateAlerting
return nil t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
}) })
UpdateAlerting = func(store dashboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
}
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3) dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1} dto.User = &models.SignedInUser{UserId: 1}
_, err := service.ImportDashboard(dto) _, err := service.ImportDashboard(dto)
So(provisioningValidated, ShouldBeTrue)
So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard) So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard)
}) })
}) })
Convey("Given provisioned dashboard", func() { Convey("Given provisioned dashboard", func() {
result := setupDeleteHandlers(true) result := setupDeleteHandlers(t, &fakeStore, true)
Convey("DeleteProvisionedDashboard should delete it", func() { Convey("DeleteProvisionedDashboard should delete it", func() {
err := service.DeleteProvisionedDashboard(1, 1) err := service.DeleteProvisionedDashboard(1, 1)
@@ -281,7 +286,7 @@ func TestDashboardService(t *testing.T) {
}) })
Convey("Given non provisioned dashboard", func() { Convey("Given non provisioned dashboard", func() {
result := setupDeleteHandlers(false) result := setupDeleteHandlers(t, &fakeStore, false)
Convey("DeleteProvisionedDashboard should delete it", func() { Convey("DeleteProvisionedDashboard should delete it", func() {
err := service.DeleteProvisionedDashboard(1, 1) err := service.DeleteProvisionedDashboard(1, 1)
@@ -306,15 +311,15 @@ type Result struct {
deleteWasCalled bool deleteWasCalled bool
} }
func setupDeleteHandlers(provisioned bool) *Result { func setupDeleteHandlers(t *testing.T, fakeStore *fakeDashboardStore, provisioned bool) *Result {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error { t.Helper()
if provisioned {
cmd.Result = &models.DashboardProvisioning{} t.Cleanup(func() {
} else { fakeStore.provisionedData = nil
cmd.Result = nil
}
return nil
}) })
if provisioned {
fakeStore.provisionedData = &models.DashboardProvisioning{}
}
result := &Result{} result := &Result{}
bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error { bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error {
@@ -326,3 +331,32 @@ func setupDeleteHandlers(provisioned bool) *Result {
return result return result
} }
type fakeDashboardStore struct {
dashboards.Store
validationError error
provisionedData *models.DashboardProvisioning
}
func (s *fakeDashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (
bool, error) {
return false, s.validationError
}
func (s *fakeDashboardStore) GetProvisionedDataByDashboardID(int64) (*models.DashboardProvisioning, error) {
return s.provisionedData, nil
}
func (s *fakeDashboardStore) SaveProvisionedDashboard(models.SaveDashboardCommand,
*models.DashboardProvisioning) (*models.Dashboard, error) {
return nil, nil
}
func (s *fakeDashboardStore) SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) {
return cmd.GetDashboardModel(), nil
}
func (s *fakeDashboardStore) SaveAlerts(dashID int64, alerts []*models.Alert) error {
return nil
}
+29 -19
View File
@@ -2,28 +2,32 @@ package dashboards
import ( import (
"errors" "errors"
"strings"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/search"
) )
// FolderService service for operating on folders // FolderService is a service for operating on folders.
type FolderService interface { type FolderService interface {
GetFolders(limit int64) ([]*models.Folder, error) GetFolders(limit int64) ([]*models.Folder, error)
GetFolderByID(id int64) (*models.Folder, error) GetFolderByID(id int64) (*models.Folder, error)
GetFolderByUID(uid string) (*models.Folder, error) GetFolderByUID(uid string) (*models.Folder, error)
CreateFolder(cmd *models.CreateFolderCommand) error CreateFolder(title, uid string) (*models.Folder, error)
UpdateFolder(uid string, cmd *models.UpdateFolderCommand) error UpdateFolder(uid string, cmd *models.UpdateFolderCommand) error
DeleteFolder(uid string) (*models.Folder, error) DeleteFolder(uid string) (*models.Folder, error)
MakeUserAdmin(orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
} }
// NewFolderService factory for creating a new folder service // NewFolderService is a factory for creating a new folder service.
var NewFolderService = func(orgId int64, user *models.SignedInUser) FolderService { var NewFolderService = func(orgID int64, user *models.SignedInUser, store dashboards.Store) FolderService {
return &dashboardServiceImpl{ return &dashboardServiceImpl{
orgId: orgId, orgId: orgID,
user: user, user: user,
dashboardStore: store,
} }
} }
@@ -58,7 +62,6 @@ func (dr *dashboardServiceImpl) GetFolders(limit int64) ([]*models.Folder, error
func (dr *dashboardServiceImpl) GetFolderByID(id int64) (*models.Folder, error) { func (dr *dashboardServiceImpl) GetFolderByID(id int64) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: id} query := models.GetDashboardQuery{OrgId: dr.orgId, Id: id}
dashFolder, err := getFolder(query) dashFolder, err := getFolder(query)
if err != nil { if err != nil {
return nil, toFolderError(err) return nil, toFolderError(err)
} }
@@ -93,8 +96,17 @@ func (dr *dashboardServiceImpl) GetFolderByUID(uid string) (*models.Folder, erro
return dashToFolder(dashFolder), nil return dashToFolder(dashFolder), nil
} }
func (dr *dashboardServiceImpl) CreateFolder(cmd *models.CreateFolderCommand) error { func (dr *dashboardServiceImpl) CreateFolder(title, uid string) (*models.Folder, error) {
dashFolder := cmd.GetDashboardModel(dr.orgId, dr.user.UserId) dashFolder := models.NewDashboardFolder(title)
dashFolder.OrgId = dr.orgId
dashFolder.SetUid(strings.TrimSpace(uid))
userID := dr.user.UserId
if userID == 0 {
userID = -1
}
dashFolder.CreatedBy = userID
dashFolder.UpdatedBy = userID
dashFolder.UpdateSlug()
dto := &SaveDashboardDTO{ dto := &SaveDashboardDTO{
Dashboard: dashFolder, Dashboard: dashFolder,
@@ -104,23 +116,21 @@ func (dr *dashboardServiceImpl) CreateFolder(cmd *models.CreateFolderCommand) er
saveDashboardCmd, err := dr.buildSaveDashboardCommand(dto, false, false) saveDashboardCmd, err := dr.buildSaveDashboardCommand(dto, false, false)
if err != nil { if err != nil {
return toFolderError(err) return nil, toFolderError(err)
} }
err = bus.Dispatch(saveDashboardCmd) dash, err := dr.dashboardStore.SaveDashboard(*saveDashboardCmd)
if err != nil { if err != nil {
return toFolderError(err) return nil, toFolderError(err)
} }
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: saveDashboardCmd.Result.Id} query := models.GetDashboardQuery{OrgId: dr.orgId, Id: dash.Id}
dashFolder, err = getFolder(query) dashFolder, err = getFolder(query)
if err != nil { if err != nil {
return toFolderError(err) return nil, toFolderError(err)
} }
cmd.Result = dashToFolder(dashFolder) return dashToFolder(dashFolder), nil
return nil
} }
func (dr *dashboardServiceImpl) UpdateFolder(existingUid string, cmd *models.UpdateFolderCommand) error { func (dr *dashboardServiceImpl) UpdateFolder(existingUid string, cmd *models.UpdateFolderCommand) error {
@@ -144,12 +154,12 @@ func (dr *dashboardServiceImpl) UpdateFolder(existingUid string, cmd *models.Upd
return toFolderError(err) return toFolderError(err)
} }
err = bus.Dispatch(saveDashboardCmd) dash, err := dr.dashboardStore.SaveDashboard(*saveDashboardCmd)
if err != nil { if err != nil {
return toFolderError(err) return toFolderError(err)
} }
query = models.GetDashboardQuery{OrgId: dr.orgId, Id: saveDashboardCmd.Result.Id} query = models.GetDashboardQuery{OrgId: dr.orgId, Id: dash.Id}
dashFolder, err = getFolder(query) dashFolder, err = getFolder(query)
if err != nil { if err != nil {
return toFolderError(err) return toFolderError(err)
+18 -29
View File
@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -15,8 +16,9 @@ import (
func TestFolderService(t *testing.T) { func TestFolderService(t *testing.T) {
Convey("Folder service tests", t, func() { Convey("Folder service tests", t, func() {
service := dashboardServiceImpl{ service := dashboardServiceImpl{
orgId: 1, orgId: 1,
user: &models.SignedInUser{UserId: 1}, user: &models.SignedInUser{UserId: 1},
dashboardStore: &fakeDashboardStore{},
} }
Convey("Given user has no permissions", func() { Convey("Given user has no permissions", func() {
@@ -28,32 +30,26 @@ func TestFolderService(t *testing.T) {
return nil return nil
}) })
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origStore := service.dashboardStore
return nil t.Cleanup(func() {
}) service.dashboardStore = origStore
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return models.ErrDashboardUpdateAccessDenied
}) })
service.dashboardStore = &fakeDashboardStore{
validationError: models.ErrDashboardUpdateAccessDenied,
}
Convey("When get folder by id should return access denied error", func() { Convey("When get folder by id should return access denied error", func() {
_, err := service.GetFolderByID(1) _, err := service.GetFolderByID(1)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied) So(err, ShouldEqual, models.ErrFolderAccessDenied)
}) })
Convey("When get folder by uid should return access denied error", func() { Convey("When get folder by uid should return access denied error", func() {
_, err := service.GetFolderByUID("uid") _, err := service.GetFolderByUID("uid")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied) So(err, ShouldEqual, models.ErrFolderAccessDenied)
}) })
Convey("When creating folder should return access denied error", func() { Convey("When creating folder should return access denied error", func() {
err := service.CreateFolder(&models.CreateFolderCommand{ _, err := service.CreateFolder("Folder", "")
Title: "Folder",
})
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied) So(err, ShouldEqual, models.ErrFolderAccessDenied)
}) })
@@ -62,7 +58,6 @@ func TestFolderService(t *testing.T) {
Uid: "uid", Uid: "uid",
Title: "Folder", Title: "Folder",
}) })
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied) So(err, ShouldEqual, models.ErrFolderAccessDenied)
}) })
@@ -89,18 +84,14 @@ func TestFolderService(t *testing.T) {
return nil return nil
}) })
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origUpdateAlerting := UpdateAlerting
return nil t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
}) })
UpdateAlerting = func(store dashboards.Store, orgID int64, dashboard *models.Dashboard,
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error { user *models.SignedInUser) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return nil return nil
}) }
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.SaveDashboardCommand) error { bus.AddHandler("test", func(cmd *models.SaveDashboardCommand) error {
cmd.Result = dash cmd.Result = dash
@@ -120,9 +111,7 @@ func TestFolderService(t *testing.T) {
}) })
Convey("When creating folder should not return access denied error", func() { Convey("When creating folder should not return access denied error", func() {
err := service.CreateFolder(&models.CreateFolderCommand{ _, err := service.CreateFolder("Folder", "")
Title: "Folder",
})
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(provisioningValidated, ShouldBeFalse) So(provisioningValidated, ShouldBeFalse)
}) })
+23 -22
View File
@@ -8,7 +8,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
@@ -67,7 +66,7 @@ func (lps *LibraryPanelService) createLibraryPanel(c *models.ReqContext, cmd cre
} }
err := lps.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error { err := lps.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
if err := requirePermissionsOnFolder(c.SignedInUser, cmd.FolderID); err != nil { if err := lps.requirePermissionsOnFolder(c.SignedInUser, cmd.FolderID); err != nil {
return err return err
} }
if _, err := session.Insert(&libraryPanel); err != nil { if _, err := session.Insert(&libraryPanel); err != nil {
@@ -108,12 +107,22 @@ func (lps *LibraryPanelService) createLibraryPanel(c *models.ReqContext, cmd cre
return dto, err return dto, err
} }
func connectDashboard(session *sqlstore.DBSession, dialect migrator.Dialect, user *models.SignedInUser, uid string, dashboardID int64) error { // connectDashboard adds a connection between a Library Panel and a Dashboard.
func (lps *LibraryPanelService) connectDashboard(c *models.ReqContext, uid string, dashboardID int64) error {
err := lps.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
return lps.implConnectDashboard(session, c.SignedInUser, uid, dashboardID)
})
return err
}
func (lps *LibraryPanelService) implConnectDashboard(session *sqlstore.DBSession, user *models.SignedInUser,
uid string, dashboardID int64) error {
panel, err := getLibraryPanel(session, uid, user.OrgId) panel, err := getLibraryPanel(session, uid, user.OrgId)
if err != nil { if err != nil {
return err return err
} }
if err := requirePermissionsOnFolder(user, panel.FolderID); err != nil { if err := lps.requirePermissionsOnFolder(user, panel.FolderID); err != nil {
return err return err
} }
@@ -126,7 +135,7 @@ func connectDashboard(session *sqlstore.DBSession, dialect migrator.Dialect, use
CreatedBy: user.UserId, CreatedBy: user.UserId,
} }
if _, err := session.Insert(&libraryPanelDashboard); err != nil { if _, err := session.Insert(&libraryPanelDashboard); err != nil {
if dialect.IsUniqueConstraintViolation(err) { if lps.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
return nil return nil
} }
return err return err
@@ -134,15 +143,6 @@ func connectDashboard(session *sqlstore.DBSession, dialect migrator.Dialect, use
return nil return nil
} }
// connectDashboard adds a connection between a Library Panel and a Dashboard.
func (lps *LibraryPanelService) connectDashboard(c *models.ReqContext, uid string, dashboardID int64) error {
err := lps.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
return connectDashboard(session, lps.SQLStore.Dialect, c.SignedInUser, uid, dashboardID)
})
return err
}
// connectLibraryPanelsForDashboard adds connections for all Library Panels in a Dashboard. // connectLibraryPanelsForDashboard adds connections for all Library Panels in a Dashboard.
func (lps *LibraryPanelService) connectLibraryPanelsForDashboard(c *models.ReqContext, uids []string, dashboardID int64) error { func (lps *LibraryPanelService) connectLibraryPanelsForDashboard(c *models.ReqContext, uids []string, dashboardID int64) error {
err := lps.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error { err := lps.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
@@ -151,7 +151,7 @@ func (lps *LibraryPanelService) connectLibraryPanelsForDashboard(c *models.ReqCo
return err return err
} }
for _, uid := range uids { for _, uid := range uids {
err := connectDashboard(session, lps.SQLStore.Dialect, c.SignedInUser, uid, dashboardID) err := lps.implConnectDashboard(session, c.SignedInUser, uid, dashboardID)
if err != nil { if err != nil {
return err return err
} }
@@ -169,7 +169,7 @@ func (lps *LibraryPanelService) deleteLibraryPanel(c *models.ReqContext, uid str
if err != nil { if err != nil {
return err return err
} }
if err := requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil { if err := lps.requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil {
return err return err
} }
if _, err := session.Exec("DELETE FROM library_panel_dashboard WHERE librarypanel_id=?", panel.ID); err != nil { if _, err := session.Exec("DELETE FROM library_panel_dashboard WHERE librarypanel_id=?", panel.ID); err != nil {
@@ -197,7 +197,7 @@ func (lps *LibraryPanelService) disconnectDashboard(c *models.ReqContext, uid st
if err != nil { if err != nil {
return err return err
} }
if err := requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil { if err := lps.requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil {
return err return err
} }
@@ -248,7 +248,7 @@ func (lps *LibraryPanelService) deleteLibraryPanelsInFolder(c *models.ReqContext
} }
folderID := folderUIDs[0].ID folderID := folderUIDs[0].ID
if err := requirePermissionsOnFolder(c.SignedInUser, folderID); err != nil { if err := lps.requirePermissionsOnFolder(c.SignedInUser, folderID); err != nil {
return err return err
} }
var dashIDs []struct { var dashIDs []struct {
@@ -496,7 +496,8 @@ func (lps *LibraryPanelService) getLibraryPanelsForDashboardID(c *models.ReqCont
return libraryPanelMap, err return libraryPanelMap, err
} }
func handleFolderIDPatches(panelToPatch *LibraryPanel, fromFolderID int64, toFolderID int64, user *models.SignedInUser) error { func (lps *LibraryPanelService) handleFolderIDPatches(panelToPatch *LibraryPanel, fromFolderID int64,
toFolderID int64, user *models.SignedInUser) error {
// FolderID was not provided in the PATCH request // FolderID was not provided in the PATCH request
if toFolderID == -1 { if toFolderID == -1 {
toFolderID = fromFolderID toFolderID = fromFolderID
@@ -504,13 +505,13 @@ func handleFolderIDPatches(panelToPatch *LibraryPanel, fromFolderID int64, toFol
// FolderID was provided in the PATCH request // FolderID was provided in the PATCH request
if toFolderID != -1 && toFolderID != fromFolderID { if toFolderID != -1 && toFolderID != fromFolderID {
if err := requirePermissionsOnFolder(user, toFolderID); err != nil { if err := lps.requirePermissionsOnFolder(user, toFolderID); err != nil {
return err return err
} }
} }
// Always check permissions for the folder where library panel resides // Always check permissions for the folder where library panel resides
if err := requirePermissionsOnFolder(user, fromFolderID); err != nil { if err := lps.requirePermissionsOnFolder(user, fromFolderID); err != nil {
return err return err
} }
@@ -551,7 +552,7 @@ func (lps *LibraryPanelService) patchLibraryPanel(c *models.ReqContext, cmd patc
if cmd.Model == nil { if cmd.Model == nil {
libraryPanel.Model = panelInDB.Model libraryPanel.Model = panelInDB.Model
} }
if err := handleFolderIDPatches(&libraryPanel, panelInDB.FolderID, cmd.FolderID, c.SignedInUser); err != nil { if err := lps.handleFolderIDPatches(&libraryPanel, panelInDB.FolderID, cmd.FolderID, c.SignedInUser); err != nil {
return err return err
} }
if err := syncTitleWithName(&libraryPanel); err != nil { if err := syncTitleWithName(&libraryPanel); err != nil {
+2 -2
View File
@@ -10,7 +10,7 @@ func isGeneralFolder(folderID int64) bool {
return folderID == 0 return folderID == 0
} }
func requirePermissionsOnFolder(user *models.SignedInUser, folderID int64) error { func (lps *LibraryPanelService) requirePermissionsOnFolder(user *models.SignedInUser, folderID int64) error {
if isGeneralFolder(folderID) && user.HasRole(models.ROLE_EDITOR) { if isGeneralFolder(folderID) && user.HasRole(models.ROLE_EDITOR) {
return nil return nil
} }
@@ -19,7 +19,7 @@ func requirePermissionsOnFolder(user *models.SignedInUser, folderID int64) error
return models.ErrFolderAccessDenied return models.ErrFolderAccessDenied
} }
s := dashboards.NewFolderService(user.OrgId, user) s := dashboards.NewFolderService(user.OrgId, user, lps.SQLStore)
folder, err := s.GetFolderByID(folderID) folder, err := s.GetFolderByID(folderID)
if err != nil { if err != nil {
return err return err
@@ -74,11 +74,11 @@ func TestGetConnectedDashboards(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to get connected dashboards for a library panel that exists and has connections, it should return connected dashboard IDs", scenarioWithLibraryPanel(t, "When an admin tries to get connected dashboards for a library panel that exists and has connections, it should return connected dashboard IDs",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
firstDash := createDashboard(t, sc.user, "Dash 1", 0) firstDash := createDashboard(t, sc.sqlStore, sc.user, "Dash 1", 0)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": strconv.FormatInt(firstDash.Id, 10)}) sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": strconv.FormatInt(firstDash.Id, 10)})
resp := sc.service.connectHandler(sc.reqContext) resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
secondDash := createDashboard(t, sc.user, "Dash 2", 0) secondDash := createDashboard(t, sc.sqlStore, sc.user, "Dash 2", 0)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": strconv.FormatInt(secondDash.Id, 10)}) sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": strconv.FormatInt(secondDash.Id, 10)})
resp = sc.service.connectHandler(sc.reqContext) resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@@ -25,7 +25,7 @@ func TestPatchLibraryPanel(t *testing.T) {
resp = sc.service.connectHandler(sc.reqContext) resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
newFolder := createFolderWithACL(t, "NewFolder", sc.user, []folderACLItem{}) newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
cmd := patchLibraryPanelCommand{ cmd := patchLibraryPanelCommand{
FolderID: newFolder.Id, FolderID: newFolder.Id,
Name: "Panel - New name", Name: "Panel - New name",
@@ -82,7 +82,7 @@ func TestPatchLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with folder only, it should change folder successfully and return correct result", scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with folder only, it should change folder successfully and return correct result",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, "NewFolder", sc.user, []folderACLItem{}) newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
cmd := patchLibraryPanelCommand{ cmd := patchLibraryPanelCommand{
FolderID: newFolder.Id, FolderID: newFolder.Id,
Version: 1, Version: 1,
@@ -174,7 +174,7 @@ func TestPatchLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with a folder where a library panel with the same name already exists, it should fail", scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with a folder where a library panel with the same name already exists, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, "NewFolder", sc.user, []folderACLItem{}) newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
command := getCreateCommand(newFolder.Id, "Text - Library Panel") command := getCreateCommand(newFolder.Id, "Text - Library Panel")
resp := sc.service.createHandler(sc.reqContext, command) resp := sc.service.createHandler(sc.reqContext, command)
var result = validateAndUnMarshalResponse(t, resp) var result = validateAndUnMarshalResponse(t, resp)
@@ -68,7 +68,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
for _, testCase := range accessCases { for _, testCase := range accessCases {
testScenario(t, fmt.Sprintf("When %s tries to create a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc), testScenario(t, fmt.Sprintf("When %s tries to create a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items) folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
sc.reqContext.SignedInUser.OrgRole = testCase.role sc.reqContext.SignedInUser.OrgRole = testCase.role
command := getCreateCommand(folder.Id, "Library Panel Name") command := getCreateCommand(folder.Id, "Library Panel Name")
@@ -78,11 +78,11 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder with %s, it should return correct status", testCase.role, testCase.desc), testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
fromFolder := createFolderWithACL(t, "Everyone", sc.user, everyonePermissions) fromFolder := createFolderWithACL(t, sc.sqlStore, "Everyone", sc.user, everyonePermissions)
command := getCreateCommand(fromFolder.Id, "Library Panel Name") command := getCreateCommand(fromFolder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command) resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
toFolder := createFolderWithACL(t, "Folder", sc.user, testCase.items) toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
sc.reqContext.SignedInUser.OrgRole = testCase.role sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryPanelCommand{FolderID: toFolder.Id, Version: 1} cmd := patchLibraryPanelCommand{FolderID: toFolder.Id, Version: 1}
@@ -93,11 +93,11 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from a folder with %s, it should return correct status", testCase.role, testCase.desc), testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
fromFolder := createFolderWithACL(t, "Everyone", sc.user, testCase.items) fromFolder := createFolderWithACL(t, sc.sqlStore, "Everyone", sc.user, testCase.items)
command := getCreateCommand(fromFolder.Id, "Library Panel Name") command := getCreateCommand(fromFolder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command) resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
toFolder := createFolderWithACL(t, "Folder", sc.user, everyonePermissions) toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
sc.reqContext.SignedInUser.OrgRole = testCase.role sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryPanelCommand{FolderID: toFolder.Id, Version: 1} cmd := patchLibraryPanelCommand{FolderID: toFolder.Id, Version: 1}
@@ -108,7 +108,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to delete a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc), testScenario(t, fmt.Sprintf("When %s tries to delete a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items) folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
cmd := getCreateCommand(folder.Id, "Library Panel Name") cmd := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -121,8 +121,8 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to connect a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc), testScenario(t, fmt.Sprintf("When %s tries to connect a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items) folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
dashboard := createDashboard(t, sc.user, "Some Folder Dash", folder.Id) dashboard := createDashboard(t, sc.sqlStore, sc.user, "Some Folder Dash", folder.Id)
cmd := getCreateCommand(folder.Id, "Library Panel Name") cmd := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -135,8 +135,8 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to disconnect a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc), testScenario(t, fmt.Sprintf("When %s tries to disconnect a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items) folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
dashboard := createDashboard(t, sc.user, "Some Folder Dash", folder.Id) dashboard := createDashboard(t, sc.sqlStore, sc.user, "Some Folder Dash", folder.Id)
cmd := getCreateCommand(folder.Id, "Library Panel Name") cmd := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -152,7 +152,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to delete all library panels in a folder with %s, it should return correct status", testCase.role, testCase.desc), testScenario(t, fmt.Sprintf("When %s tries to delete all library panels in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items) folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
cmd := getCreateCommand(folder.Id, "Library Panel Name") cmd := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
validateAndUnMarshalResponse(t, resp) validateAndUnMarshalResponse(t, resp)
@@ -191,7 +191,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to the General folder, it should return correct status", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, everyonePermissions) folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
command := getCreateCommand(folder.Id, "Library Panel Name") command := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command) resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -205,7 +205,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from the General folder, it should return correct status", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, everyonePermissions) folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
command := getCreateCommand(0, "Library Panel Name") command := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command) resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -231,7 +231,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to connect a library panel in the General folder, it should return correct status", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to connect a library panel in the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
dashboard := createDashboard(t, sc.user, "General Folder Dash", 0) dashboard := createDashboard(t, sc.sqlStore, sc.user, "General Folder Dash", 0)
cmd := getCreateCommand(0, "Library Panel Name") cmd := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -244,7 +244,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to disconnect a library panel in the General folder, it should return correct status", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to disconnect a library panel in the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
dashboard := createDashboard(t, sc.user, "General Folder Dash", 0) dashboard := createDashboard(t, sc.sqlStore, sc.user, "General Folder Dash", 0)
cmd := getCreateCommand(0, "Library Panel Name") cmd := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -260,7 +260,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to get connected dashboards in the General folder for a library panel in the General folder, it should return correct status", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to get connected dashboards in the General folder for a library panel in the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
dashboard := createDashboard(t, sc.user, "General Folder Dash", 0) dashboard := createDashboard(t, sc.sqlStore, sc.user, "General Folder Dash", 0)
cmd := getCreateCommand(0, "Library Panel Name") cmd := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -301,7 +301,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", testCase.role), testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", testCase.role),
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, everyonePermissions) folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
command := getCreateCommand(folder.Id, "Library Panel Name") command := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command) resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -329,7 +329,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
var results []libraryPanel var results []libraryPanel
for i, folderCase := range folderCases { for i, folderCase := range folderCases {
folder := createFolderWithACL(t, fmt.Sprintf("Folder%v", i), sc.user, folderCase) folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
cmd := getCreateCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i)) cmd := getCreateCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i))
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -401,8 +401,8 @@ func TestLibraryPanelPermissions(t *testing.T) {
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
for i, folderCase := range folderCases { for i, folderCase := range folderCases {
folder := createFolderWithACL(t, fmt.Sprintf("Folder%v", i), sc.user, folderCase) folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
dashboard := createDashboard(t, sc.user, "Some Folder Dash", folder.Id) dashboard := createDashboard(t, sc.sqlStore, sc.user, "Some Folder Dash", folder.Id)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)}) sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)})
resp = sc.service.connectHandler(sc.reqContext) resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
@@ -434,7 +434,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
var results []libraryPanel var results []libraryPanel
for i, folderCase := range folderCases { for i, folderCase := range folderCases {
folder := createFolderWithACL(t, fmt.Sprintf("Folder%v", i), sc.user, folderCase) folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
cmd := getCreateCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i)) cmd := getCreateCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i))
resp := sc.service.createHandler(sc.reqContext, cmd) resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp) result := validateAndUnMarshalResponse(t, resp)
@@ -13,8 +13,8 @@ import (
"gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
@@ -731,6 +731,7 @@ type scenarioContext struct {
user models.SignedInUser user models.SignedInUser
folder *models.Folder folder *models.Folder
initialResult libraryPanelResult initialResult libraryPanelResult
sqlStore *sqlstore.SQLStore
} }
type folderACLItem struct { type folderACLItem struct {
@@ -738,7 +739,8 @@ type folderACLItem struct {
permission models.PermissionType permission models.PermissionType
} }
func createDashboard(t *testing.T, user models.SignedInUser, title string, folderID int64) *models.Dashboard { func createDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, user models.SignedInUser, title string,
folderID int64) *models.Dashboard {
dash := models.NewDashboard(title) dash := models.NewDashboard(title)
dash.FolderId = folderID dash.FolderId = folderID
dashItem := &dashboards.SaveDashboardDTO{ dashItem := &dashboards.SaveDashboardDTO{
@@ -748,53 +750,47 @@ func createDashboard(t *testing.T, user models.SignedInUser, title string, folde
User: &user, User: &user,
Overwrite: false, Overwrite: false,
} }
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { origUpdateAlerting := dashboards.UpdateAlerting
return nil t.Cleanup(func() {
dashboards.UpdateAlerting = origUpdateAlerting
}) })
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error { dashboards.UpdateAlerting = func(store dboards.Store, orgID int64, dashboard *models.Dashboard,
cmd.Result = &models.ValidateDashboardBeforeSaveResult{} user *models.SignedInUser) error {
return nil return nil
}) }
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
})
dashboard, err := dashboards.NewService().SaveDashboard(dashItem, true) dashboard, err := dashboards.NewService(sqlStore).SaveDashboard(dashItem, true)
require.NoError(t, err) require.NoError(t, err)
return dashboard return dashboard
} }
func createFolderWithACL(t *testing.T, title string, user models.SignedInUser, items []folderACLItem) *models.Folder { func createFolderWithACL(t *testing.T, sqlStore *sqlstore.SQLStore, title string, user models.SignedInUser,
s := dashboards.NewFolderService(user.OrgId, &user) items []folderACLItem) *models.Folder {
folderCmd := models.CreateFolderCommand{ t.Helper()
Uid: title,
Title: title, s := dashboards.NewFolderService(user.OrgId, &user, sqlStore)
} t.Logf("Creating folder with title and UID %q", title)
err := s.CreateFolder(&folderCmd) folder, err := s.CreateFolder(title, title)
require.NoError(t, err) require.NoError(t, err)
updateFolderACL(t, folderCmd.Result.Id, items) updateFolderACL(t, sqlStore, folder.Id, items)
return folderCmd.Result return folder
} }
func updateFolderACL(t *testing.T, folderID int64, items []folderACLItem) { func updateFolderACL(t *testing.T, sqlStore *sqlstore.SQLStore, folderID int64, items []folderACLItem) {
t.Helper()
if len(items) == 0 { if len(items) == 0 {
return return
} }
cmd := models.UpdateDashboardAclCommand{ var aclItems []*models.DashboardAcl
DashboardID: folderID,
}
for _, item := range items { for _, item := range items {
role := item.roleType role := item.roleType
permission := item.permission permission := item.permission
cmd.Items = append(cmd.Items, &models.DashboardAcl{ aclItems = append(aclItems, &models.DashboardAcl{
DashboardID: folderID, DashboardID: folderID,
Role: &role, Role: &role,
Permission: permission, Permission: permission,
@@ -803,11 +799,13 @@ func updateFolderACL(t *testing.T, folderID int64, items []folderACLItem) {
}) })
} }
err := bus.Dispatch(&cmd) err := sqlStore.UpdateDashboardACL(folderID, aclItems)
require.NoError(t, err) require.NoError(t, err)
} }
func validateAndUnMarshalResponse(t *testing.T, resp response.Response) libraryPanelResult { func validateAndUnMarshalResponse(t *testing.T, resp response.Response) libraryPanelResult {
t.Helper()
require.Equal(t, 200, resp.Status()) require.Equal(t, 200, resp.Status())
var result = libraryPanelResult{} var result = libraryPanelResult{}
@@ -818,6 +816,8 @@ func validateAndUnMarshalResponse(t *testing.T, resp response.Response) libraryP
} }
func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) { func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
t.Helper()
testScenario(t, desc, func(t *testing.T, sc scenarioContext) { testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Text - Library Panel") command := getCreateCommand(sc.folder.Id, "Text - Library Panel")
resp := sc.service.createHandler(sc.reqContext, command) resp := sc.service.createHandler(sc.reqContext, command)
@@ -865,25 +865,26 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
// deliberate difference between signed in user and user in db to make it crystal clear // deliberate difference between signed in user and user in db to make it crystal clear
// what to expect in the tests // what to expect in the tests
// In the real world these are identical // In the real world these are identical
cmd := &models.CreateUserCommand{ cmd := models.CreateUserCommand{
Email: "user.in.db@test.com", Email: "user.in.db@test.com",
Name: "User In DB", Name: "User In DB",
Login: UserInDbName, Login: UserInDbName,
} }
err := sqlstore.CreateUser(context.Background(), cmd) _, err := sqlStore.CreateUser(context.Background(), cmd)
require.NoError(t, err) require.NoError(t, err)
sc := scenarioContext{ sc := scenarioContext{
user: user, user: user,
ctx: &ctx, ctx: &ctx,
service: &service, service: &service,
sqlStore: sqlStore,
reqContext: &models.ReqContext{ reqContext: &models.ReqContext{
Context: &ctx, Context: &ctx,
SignedInUser: &user, SignedInUser: &user,
}, },
} }
sc.folder = createFolderWithACL(t, "ScenarioFolder", sc.user, []folderACLItem{}) sc.folder = createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{})
fn(t, sc) fn(t, sc)
}) })
+4 -3
View File
@@ -8,7 +8,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"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/plugins/manager" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/live/features" "github.com/grafana/grafana/pkg/services/live/features"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@@ -43,6 +43,7 @@ type GrafanaLive struct {
Cfg *setting.Cfg `inject:""` Cfg *setting.Cfg `inject:""`
RouteRegister routing.RouteRegister `inject:""` RouteRegister routing.RouteRegister `inject:""`
LogsService *cloudwatch.LogsService `inject:""` LogsService *cloudwatch.LogsService `inject:""`
PluginManager plugins.Manager `inject:""`
node *centrifuge.Node node *centrifuge.Node
// The websocket handler // The websocket handler
@@ -222,8 +223,8 @@ func (g *GrafanaLive) GetChannelHandlerFactory(scope string, name string) (model
}, nil }, nil
} }
p, ok := manager.Plugins[name] p := g.PluginManager.GetPlugin(name)
if ok { if p != nil {
h := &PluginHandler{ h := &PluginHandler{
Plugin: p, Plugin: p,
} }
+3 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/sqlstore"
) )
func init() { func init() {
@@ -21,6 +22,7 @@ var (
type TeamSyncFunc func(user *models.User, externalUser *models.ExternalUserInfo) error type TeamSyncFunc func(user *models.User, externalUser *models.ExternalUserInfo) error
type LoginService struct { type LoginService struct {
SQLStore *sqlstore.SQLStore `inject:""`
Bus bus.Bus `inject:""` Bus bus.Bus `inject:""`
QuotaService *quota.QuotaService `inject:""` QuotaService *quota.QuotaService `inject:""`
TeamSync TeamSyncFunc TeamSync TeamSyncFunc
@@ -107,7 +109,7 @@ func (ls *LoginService) UpsertUser(cmd *models.UpsertUserCommand) error {
// Sync isGrafanaAdmin permission // Sync isGrafanaAdmin permission
if extUser.IsGrafanaAdmin != nil && *extUser.IsGrafanaAdmin != cmd.Result.IsAdmin { if extUser.IsGrafanaAdmin != nil && *extUser.IsGrafanaAdmin != cmd.Result.IsAdmin {
if err := ls.Bus.Dispatch(&models.UpdateUserPermissionsCommand{UserId: cmd.Result.Id, IsGrafanaAdmin: *extUser.IsGrafanaAdmin}); err != nil { if err := ls.SQLStore.UpdateUserPermissions(cmd.Result.Id, *extUser.IsGrafanaAdmin); err != nil {
return err return err
} }
} }
@@ -6,8 +6,10 @@ import (
"os" "os"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"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/plugins"
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
) )
@@ -22,7 +24,7 @@ type DashboardProvisioner interface {
} }
// DashboardProvisionerFactory creates DashboardProvisioners based on input // DashboardProvisionerFactory creates DashboardProvisioners based on input
type DashboardProvisionerFactory func(string) (DashboardProvisioner, error) type DashboardProvisionerFactory func(string, dashboards.Store, plugins.DataRequestHandler) (DashboardProvisioner, error)
// Provisioner is responsible for syncing dashboard from disk to Grafana's database. // Provisioner is responsible for syncing dashboard from disk to Grafana's database.
type Provisioner struct { type Provisioner struct {
@@ -32,16 +34,15 @@ type Provisioner struct {
} }
// New returns a new DashboardProvisioner // New returns a new DashboardProvisioner
func New(configDirectory string) (*Provisioner, error) { func New(configDirectory string, store dashboards.Store, reqHandler plugins.DataRequestHandler) (DashboardProvisioner, error) {
logger := log.New("provisioning.dashboard") logger := log.New("provisioning.dashboard")
cfgReader := &configReader{path: configDirectory, log: logger} cfgReader := &configReader{path: configDirectory, log: logger}
configs, err := cfgReader.readConfig() configs, err := cfgReader.readConfig()
if err != nil { if err != nil {
return nil, errutil.Wrap("Failed to read dashboards config", err) return nil, errutil.Wrap("Failed to read dashboards config", err)
} }
fileReaders, err := getFileReaders(configs, logger) fileReaders, err := getFileReaders(configs, logger, store)
if err != nil { if err != nil {
return nil, errutil.Wrap("Failed to initialize file readers", err) return nil, errutil.Wrap("Failed to initialize file readers", err)
} }
@@ -115,13 +116,14 @@ func (provider *Provisioner) GetAllowUIUpdatesFromConfig(name string) bool {
return false return false
} }
func getFileReaders(configs []*config, logger log.Logger) ([]*FileReader, error) { func getFileReaders(configs []*config, logger log.Logger, store dashboards.Store) ([]*FileReader, error) {
var readers []*FileReader var readers []*FileReader
for _, config := range configs { for _, config := range configs {
switch config.Type { switch config.Type {
case "file": case "file":
fileReader, err := NewDashboardFileReader(config, logger.New("type", config.Type, "name", config.Name)) fileReader, err := NewDashboardFileReader(config, logger.New("type", config.Type, "name", config.Name),
store)
if err != nil { if err != nil {
return nil, errutil.Wrapf(err, "Failed to create file reader for config %v", config.Name) return nil, errutil.Wrapf(err, "Failed to create file reader for config %v", config.Name)
} }
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
dboards "github.com/grafana/grafana/pkg/dashboards"
"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/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
@@ -35,7 +36,7 @@ type FileReader struct {
} }
// NewDashboardFileReader returns a new filereader based on `config` // NewDashboardFileReader returns a new filereader based on `config`
func NewDashboardFileReader(cfg *config, log log.Logger) (*FileReader, error) { func NewDashboardFileReader(cfg *config, log log.Logger, store dboards.Store) (*FileReader, error) {
var path string var path string
path, ok := cfg.Options["path"].(string) path, ok := cfg.Options["path"].(string)
if !ok { if !ok {
@@ -56,7 +57,7 @@ func NewDashboardFileReader(cfg *config, log log.Logger) (*FileReader, error) {
Cfg: cfg, Cfg: cfg,
Path: path, Path: path,
log: log, log: log,
dashboardProvisioningService: dashboards.NewProvisioningService(), dashboardProvisioningService: dashboards.NewProvisioningService(store),
FoldersFromFilesStructure: foldersFromFilesStructure, FoldersFromFilesStructure: foldersFromFilesStructure,
}, nil }, nil
} }
@@ -24,7 +24,7 @@ func TestProvisionedSymlinkedFolder(t *testing.T) {
Options: map[string]interface{}{"path": symlinkedFolder}, Options: map[string]interface{}{"path": symlinkedFolder},
} }
reader, err := NewDashboardFileReader(cfg, log.New("test-logger")) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
require.NoError(t, err) require.NoError(t, err)
want, err := filepath.Abs(containingID) want, err := filepath.Abs(containingID)
@@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@@ -41,14 +42,14 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
Convey("using path parameter", func() { Convey("using path parameter", func() {
cfg.Options["path"] = defaultDashboards cfg.Options["path"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, log.New("test-logger")) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(reader.Path, ShouldNotEqual, "") So(reader.Path, ShouldNotEqual, "")
}) })
Convey("using folder as options", func() { Convey("using folder as options", func() {
cfg.Options["folder"] = defaultDashboards cfg.Options["folder"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, log.New("test-logger")) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(reader.Path, ShouldNotEqual, "") So(reader.Path, ShouldNotEqual, "")
}) })
@@ -56,7 +57,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
Convey("using foldersFromFilesStructure as options", func() { Convey("using foldersFromFilesStructure as options", func() {
cfg.Options["path"] = foldersFromFilesStructure cfg.Options["path"] = foldersFromFilesStructure
cfg.Options["foldersFromFilesStructure"] = true cfg.Options["foldersFromFilesStructure"] = true
reader, err := NewDashboardFileReader(cfg, log.New("test-logger")) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(reader.Path, ShouldNotEqual, "") So(reader.Path, ShouldNotEqual, "")
}) })
@@ -68,7 +69,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
} }
cfg.Options["folder"] = fullPath cfg.Options["folder"] = fullPath
reader, err := NewDashboardFileReader(cfg, log.New("test-logger")) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(reader.Path, ShouldEqual, fullPath) So(reader.Path, ShouldEqual, fullPath)
@@ -77,7 +78,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
Convey("using relative path", func() { Convey("using relative path", func() {
cfg.Options["folder"] = defaultDashboards cfg.Options["folder"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, log.New("test-logger")) reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
resolvedPath := reader.resolvedPath() resolvedPath := reader.resolvedPath()
@@ -111,7 +112,7 @@ func TestDashboardFileReader(t *testing.T) {
cfg.Options["path"] = defaultDashboards cfg.Options["path"] = defaultDashboards
cfg.Folder = "Team A" cfg.Folder = "Team A"
reader, err := NewDashboardFileReader(cfg, logger) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = reader.walkDisk() err = reader.walkDisk()
@@ -142,7 +143,7 @@ func TestDashboardFileReader(t *testing.T) {
Slug: "grafana", Slug: "grafana",
}) })
reader, err := NewDashboardFileReader(cfg, logger) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = reader.walkDisk() err = reader.walkDisk()
@@ -154,7 +155,7 @@ func TestDashboardFileReader(t *testing.T) {
Convey("Overrides id from dashboard.json files", func() { Convey("Overrides id from dashboard.json files", func() {
cfg.Options["path"] = containingID cfg.Options["path"] = containingID
reader, err := NewDashboardFileReader(cfg, logger) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = reader.walkDisk() err = reader.walkDisk()
@@ -167,7 +168,7 @@ func TestDashboardFileReader(t *testing.T) {
cfg.Options["path"] = foldersFromFilesStructure cfg.Options["path"] = foldersFromFilesStructure
cfg.Options["foldersFromFilesStructure"] = true cfg.Options["foldersFromFilesStructure"] = true
reader, err := NewDashboardFileReader(cfg, logger) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = reader.walkDisk() err = reader.walkDisk()
@@ -211,14 +212,14 @@ func TestDashboardFileReader(t *testing.T) {
Folder: "", Folder: "",
} }
_, err := NewDashboardFileReader(cfg, logger) _, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("Broken dashboards should not cause error", func() { Convey("Broken dashboards should not cause error", func() {
cfg.Options["path"] = brokenDashboards cfg.Options["path"] = brokenDashboards
_, err := NewDashboardFileReader(cfg, logger) _, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@@ -226,13 +227,13 @@ func TestDashboardFileReader(t *testing.T) {
cfg1 := &config{Name: "1", Type: "file", OrgID: 1, Folder: "f1", Options: map[string]interface{}{"path": containingID}} cfg1 := &config{Name: "1", Type: "file", OrgID: 1, Folder: "f1", Options: map[string]interface{}{"path": containingID}}
cfg2 := &config{Name: "2", Type: "file", OrgID: 1, Folder: "f2", Options: map[string]interface{}{"path": containingID}} cfg2 := &config{Name: "2", Type: "file", OrgID: 1, Folder: "f2", Options: map[string]interface{}{"path": containingID}}
reader1, err := NewDashboardFileReader(cfg1, logger) reader1, err := NewDashboardFileReader(cfg1, logger, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = reader1.walkDisk() err = reader1.walkDisk()
So(err, ShouldBeNil) So(err, ShouldBeNil)
reader2, err := NewDashboardFileReader(cfg2, logger) reader2, err := NewDashboardFileReader(cfg2, logger, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = reader2.walkDisk() err = reader2.walkDisk()
@@ -336,7 +337,7 @@ func TestDashboardFileReader(t *testing.T) {
Convey("Missing dashboard should be unprovisioned if DisableDeletion = true", func() { Convey("Missing dashboard should be unprovisioned if DisableDeletion = true", func() {
cfg.DisableDeletion = true cfg.DisableDeletion = true
reader, err := NewDashboardFileReader(cfg, logger) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = reader.walkDisk() err = reader.walkDisk()
@@ -347,7 +348,7 @@ func TestDashboardFileReader(t *testing.T) {
}) })
Convey("Missing dashboard should be deleted if DisableDeletion = false", func() { Convey("Missing dashboard should be deleted if DisableDeletion = false", func() {
reader, err := NewDashboardFileReader(cfg, logger) reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = reader.walkDisk() err = reader.walkDisk()
@@ -395,7 +396,7 @@ func mockDashboardProvisioningService() *fakeDashboardProvisioningService {
mock := fakeDashboardProvisioningService{ mock := fakeDashboardProvisioningService{
provisioned: map[string][]*models.DashboardProvisioning{}, provisioned: map[string][]*models.DashboardProvisioning{},
} }
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService { dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
return &mock return &mock
} }
return &mock return &mock
@@ -55,7 +55,7 @@ func (cr *configReaderImpl) readConfig(path string) ([]*pluginsAsConfig, error)
checkOrgIDAndOrgName(apps) checkOrgIDAndOrgName(apps)
err = validatePluginsConfig(apps) err = cr.validatePluginsConfig(apps)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -105,7 +105,7 @@ func validateRequiredField(apps []*pluginsAsConfig) error {
return nil return nil
} }
func validatePluginsConfig(apps []*pluginsAsConfig) error { func (cr *configReaderImpl) validatePluginsConfig(apps []*pluginsAsConfig) error {
for i := range apps { for i := range apps {
if apps[i].Apps == nil { if apps[i].Apps == nil {
continue continue
+9 -7
View File
@@ -6,11 +6,13 @@ import (
"sync" "sync"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
plugifaces "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/provisioning/dashboards" "github.com/grafana/grafana/pkg/services/provisioning/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning/datasources" "github.com/grafana/grafana/pkg/services/provisioning/datasources"
"github.com/grafana/grafana/pkg/services/provisioning/notifiers" "github.com/grafana/grafana/pkg/services/provisioning/notifiers"
"github.com/grafana/grafana/pkg/services/provisioning/plugins" "github.com/grafana/grafana/pkg/services/provisioning/plugins"
"github.com/grafana/grafana/pkg/services/sqlstore"
"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"
) )
@@ -27,10 +29,8 @@ type ProvisioningService interface {
func init() { func init() {
registry.Register(&registry.Descriptor{ registry.Register(&registry.Descriptor{
Name: "ProvisioningService", Name: "ProvisioningService",
Instance: NewProvisioningServiceImpl( Instance: newProvisioningServiceImpl(
func(path string) (dashboards.DashboardProvisioner, error) { dashboards.New,
return dashboards.New(path)
},
notifiers.Provision, notifiers.Provision,
datasources.Provision, datasources.Provision,
plugins.Provision, plugins.Provision,
@@ -39,7 +39,7 @@ func init() {
}) })
} }
func NewProvisioningServiceImpl( func newProvisioningServiceImpl(
newDashboardProvisioner dashboards.DashboardProvisionerFactory, newDashboardProvisioner dashboards.DashboardProvisionerFactory,
provisionNotifiers func(string) error, provisionNotifiers func(string) error,
provisionDatasources func(string) error, provisionDatasources func(string) error,
@@ -55,7 +55,9 @@ func NewProvisioningServiceImpl(
} }
type provisioningServiceImpl struct { type provisioningServiceImpl struct {
Cfg *setting.Cfg `inject:""` Cfg *setting.Cfg `inject:""`
RequestHandler plugifaces.DataRequestHandler `inject:""`
SQLStore *sqlstore.SQLStore `inject:""`
log log.Logger log log.Logger
pollingCtxCancel context.CancelFunc pollingCtxCancel context.CancelFunc
newDashboardProvisioner dashboards.DashboardProvisionerFactory newDashboardProvisioner dashboards.DashboardProvisionerFactory
@@ -134,7 +136,7 @@ func (ps *provisioningServiceImpl) ProvisionNotifications() error {
func (ps *provisioningServiceImpl) ProvisionDashboards() error { func (ps *provisioningServiceImpl) ProvisionDashboards() error {
dashboardPath := filepath.Join(ps.Cfg.ProvisioningPath, "dashboards") dashboardPath := filepath.Join(ps.Cfg.ProvisioningPath, "dashboards")
dashProvisioner, err := ps.newDashboardProvisioner(dashboardPath) dashProvisioner, err := ps.newDashboardProvisioner(dashboardPath, ps.SQLStore, ps.RequestHandler)
if err != nil { if err != nil {
return errutil.Wrap("Failed to create provisioner", err) return errutil.Wrap("Failed to create provisioner", err)
} }
@@ -6,6 +6,8 @@ import (
"testing" "testing"
"time" "time"
dboards "github.com/grafana/grafana/pkg/dashboards"
plugifaces "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/provisioning/dashboards" "github.com/grafana/grafana/pkg/services/provisioning/dashboards"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -90,8 +92,8 @@ func setup() *serviceTestStruct {
pollChangesChannel <- ctx pollChangesChannel <- ctx
} }
serviceTest.service = NewProvisioningServiceImpl( serviceTest.service = newProvisioningServiceImpl(
func(path string) (dashboards.DashboardProvisioner, error) { func(string, dboards.Store, plugifaces.DataRequestHandler) (dashboards.DashboardProvisioner, error) {
return serviceTest.mock, nil return serviceTest.mock, nil
}, },
nil, nil,
+3 -3
View File
@@ -17,7 +17,6 @@ import (
"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/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@@ -50,6 +49,7 @@ type RenderingService struct {
Cfg *setting.Cfg `inject:""` Cfg *setting.Cfg `inject:""`
RemoteCacheService *remotecache.RemoteCache `inject:""` RemoteCacheService *remotecache.RemoteCache `inject:""`
PluginManager plugins.Manager `inject:""`
} }
func (rs *RenderingService) Init() error { func (rs *RenderingService) Init() error {
@@ -87,7 +87,7 @@ func (rs *RenderingService) Run(ctx context.Context) error {
if rs.pluginAvailable() { if rs.pluginAvailable() {
rs.log = rs.log.New("renderer", "plugin") rs.log = rs.log.New("renderer", "plugin")
rs.pluginInfo = manager.Renderer rs.pluginInfo = rs.PluginManager.Renderer()
if err := rs.startPlugin(ctx); err != nil { if err := rs.startPlugin(ctx); err != nil {
return err return err
@@ -107,7 +107,7 @@ func (rs *RenderingService) Run(ctx context.Context) error {
} }
func (rs *RenderingService) pluginAvailable() bool { func (rs *RenderingService) pluginAvailable() bool {
return manager.Renderer != nil return rs.PluginManager.Renderer() != nil
} }
func (rs *RenderingService) remoteAvailable() bool { func (rs *RenderingService) remoteAvailable() bool {
+29 -9
View File
@@ -2,6 +2,7 @@ package sqlstore
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@@ -166,18 +167,18 @@ func deleteAlertDefinition(dashboardId int64, sess *DBSession) error {
return nil return nil
} }
func SaveAlerts(cmd *models.SaveAlertsCommand) error { func (ss *SQLStore) SaveAlerts(dashID int64, alerts []*models.Alert) error {
return inTransaction(func(sess *DBSession) error { return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
existingAlerts, err := GetAlertsByDashboardId2(cmd.DashboardId, sess) existingAlerts, err := GetAlertsByDashboardId2(dashID, sess)
if err != nil { if err != nil {
return err return err
} }
if err := updateAlerts(existingAlerts, cmd, sess); err != nil { if err := updateAlerts(existingAlerts, alerts, sess); err != nil {
return err return err
} }
if err := deleteMissingAlerts(existingAlerts, cmd, sess); err != nil { if err := deleteMissingAlerts(existingAlerts, alerts, sess); err != nil {
return err return err
} }
@@ -185,8 +186,27 @@ func SaveAlerts(cmd *models.SaveAlertsCommand) error {
}) })
} }
func updateAlerts(existingAlerts []*models.Alert, cmd *models.SaveAlertsCommand, sess *DBSession) error { func SaveAlerts(cmd *models.SaveAlertsCommand) error {
for _, alert := range cmd.Alerts { return inTransaction(func(sess *DBSession) error {
existingAlerts, err := GetAlertsByDashboardId2(cmd.DashboardId, sess)
if err != nil {
return err
}
if err := updateAlerts(existingAlerts, cmd.Alerts, sess); err != nil {
return err
}
if err := deleteMissingAlerts(existingAlerts, cmd.Alerts, sess); err != nil {
return err
}
return nil
})
}
func updateAlerts(existingAlerts []*models.Alert, alerts []*models.Alert, sess *DBSession) error {
for _, alert := range alerts {
update := false update := false
var alertToUpdate *models.Alert var alertToUpdate *models.Alert
@@ -245,11 +265,11 @@ func updateAlerts(existingAlerts []*models.Alert, cmd *models.SaveAlertsCommand,
return nil return nil
} }
func deleteMissingAlerts(alerts []*models.Alert, cmd *models.SaveAlertsCommand, sess *DBSession) error { func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, sess *DBSession) error {
for _, missingAlert := range alerts { for _, missingAlert := range alerts {
missing := true missing := true
for _, k := range cmd.Alerts { for _, k := range existingAlerts {
if missingAlert.PanelId == k.PanelId { if missingAlert.PanelId == k.PanelId {
missing = false missing = false
break break
+9 -7
View File
@@ -30,10 +30,11 @@ func TestAlertingDataAccess(t *testing.T) {
defer resetTimeNow() defer resetTimeNow()
Convey("Testing Alerting data access", t, func() { Convey("Testing Alerting data access", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
testDash := insertTestDashboard(t, "dashboard with alerts", 1, 0, false, "alert") testDash := insertTestDashboard(t, sqlStore, "dashboard with alerts", 1, 0, false, "alert")
evalData, _ := simplejson.NewJson([]byte(`{"test": "test"}`)) evalData, err := simplejson.NewJson([]byte(`{"test": "test"}`))
So(err, ShouldBeNil)
items := []*models.Alert{ items := []*models.Alert{
{ {
PanelId: 1, PanelId: 1,
@@ -54,7 +55,7 @@ func TestAlertingDataAccess(t *testing.T) {
UserId: 1, UserId: 1,
} }
err := SaveAlerts(&cmd) err = SaveAlerts(&cmd)
Convey("Can create one alert", func() { Convey("Can create one alert", func() {
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -271,10 +272,11 @@ func TestPausingAlerts(t *testing.T) {
defer resetTimeNow() defer resetTimeNow()
Convey("Given an alert", t, func() { Convey("Given an alert", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
testDash := insertTestDashboard(t, "dashboard with alerts", 1, 0, false, "alert") testDash := insertTestDashboard(t, sqlStore, "dashboard with alerts", 1, 0, false, "alert")
alert, _ := insertTestAlert("Alerting title", "Alerting message", testDash.OrgId, testDash.Id, simplejson.New()) alert, err := insertTestAlert("Alerting title", "Alerting message", testDash.OrgId, testDash.Id, simplejson.New())
So(err, ShouldBeNil)
stateDateBeforePause := alert.NewStateDate stateDateBeforePause := alert.NewStateDate
stateDateAfterPause := stateDateBeforePause stateDateAfterPause := stateDateBeforePause
+76 -44
View File
@@ -1,6 +1,8 @@
package sqlstore package sqlstore
import ( import (
"context"
"fmt"
"strings" "strings"
"time" "time"
@@ -24,7 +26,6 @@ var shadowSearchCounter = prometheus.NewCounterVec(
) )
func init() { func init() {
bus.AddHandler("sql", SaveDashboard)
bus.AddHandler("sql", GetDashboard) bus.AddHandler("sql", GetDashboard)
bus.AddHandler("sql", GetDashboards) bus.AddHandler("sql", GetDashboards)
bus.AddHandler("sql", DeleteDashboard) bus.AddHandler("sql", DeleteDashboard)
@@ -35,7 +36,6 @@ func init() {
bus.AddHandler("sql", GetDashboardsByPluginId) bus.AddHandler("sql", GetDashboardsByPluginId)
bus.AddHandler("sql", GetDashboardPermissionsForUser) bus.AddHandler("sql", GetDashboardPermissionsForUser)
bus.AddHandler("sql", GetDashboardsBySlug) bus.AddHandler("sql", GetDashboardsBySlug)
bus.AddHandler("sql", ValidateDashboardBeforeSave)
bus.AddHandler("sql", HasEditPermissionInFolders) bus.AddHandler("sql", HasEditPermissionInFolders)
bus.AddHandler("sql", HasAdminPermissionInFolders) bus.AddHandler("sql", HasAdminPermissionInFolders)
@@ -44,10 +44,11 @@ func init() {
var generateNewUid func() string = util.GenerateShortUID var generateNewUid func() string = util.GenerateShortUID
func SaveDashboard(cmd *models.SaveDashboardCommand) error { func (ss *SQLStore) SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) {
return inTransaction(func(sess *DBSession) error { err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
return saveDashboard(sess, cmd) return saveDashboard(sess, &cmd)
}) })
return cmd.Result, err
} }
func saveDashboard(sess *DBSession, cmd *models.SaveDashboardCommand) error { func saveDashboard(sess *DBSession, cmd *models.SaveDashboardCommand) error {
@@ -162,7 +163,7 @@ func saveDashboard(sess *DBSession, cmd *models.SaveDashboardCommand) error {
cmd.Result = dash cmd.Result = dash
return err return nil
} }
func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) { func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
@@ -182,6 +183,26 @@ func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
return "", models.ErrDashboardFailedGenerateUniqueUid return "", models.ErrDashboardFailedGenerateUniqueUid
} }
// GetDashboard gets a dashboard.
func (ss *SQLStore) GetDashboard(id, orgID int64, uid, slug string) (*models.Dashboard, error) {
if id == 0 && slug == "" && uid == "" {
return nil, models.ErrDashboardIdentifierNotSet
}
dashboard := models.Dashboard{Slug: slug, OrgId: orgID, Id: id, Uid: uid}
has, err := ss.engine.Get(&dashboard)
if err != nil {
return nil, err
} else if !has {
return nil, models.ErrDashboardNotFound
}
dashboard.SetId(dashboard.Id)
dashboard.SetUid(dashboard.Uid)
return &dashboard, nil
}
// TODO: Remove me
func GetDashboard(query *models.GetDashboardQuery) error { func GetDashboard(query *models.GetDashboardQuery) error {
if query.Id == 0 && len(query.Slug) == 0 && len(query.Uid) == 0 { if query.Id == 0 && len(query.Slug) == 0 && len(query.Uid) == 0 {
return models.ErrDashboardIdentifierNotSet return models.ErrDashboardIdentifierNotSet
@@ -573,20 +594,20 @@ func GetDashboardUIDById(query *models.GetDashboardRefByIdQuery) error {
return nil return nil
} }
func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *models.ValidateDashboardBeforeSaveCommand) (err error) { func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, dash *models.Dashboard, overwrite bool) (bool, error) {
dash := cmd.Dashboard
dashWithIdExists := false dashWithIdExists := false
isParentFolderChanged := false
var existingById models.Dashboard var existingById models.Dashboard
if dash.Id > 0 { if dash.Id > 0 {
var err error
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById) dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
if err != nil { if err != nil {
return err return isParentFolderChanged, fmt.Errorf("SQL query for existing dashboard by ID failed: %w", err)
} }
if !dashWithIdExists { if !dashWithIdExists {
return models.ErrDashboardNotFound return isParentFolderChanged, models.ErrDashboardNotFound
} }
if dash.Uid == "" { if dash.Uid == "" {
@@ -598,30 +619,32 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *models.Validat
var existingByUid models.Dashboard var existingByUid models.Dashboard
if dash.Uid != "" { if dash.Uid != "" {
var err error
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid) dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
if err != nil { if err != nil {
return err return isParentFolderChanged, fmt.Errorf("SQL query for existing dashboard by UID failed: %w", err)
} }
} }
if dash.FolderId > 0 { if dash.FolderId > 0 {
var existingFolder models.Dashboard var existingFolder models.Dashboard
folderExists, folderErr := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId, dialect.BooleanStr(true)).Get(&existingFolder) folderExists, err := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId,
if folderErr != nil { dialect.BooleanStr(true)).Get(&existingFolder)
return folderErr if err != nil {
return isParentFolderChanged, fmt.Errorf("SQL query for folder failed: %w", err)
} }
if !folderExists { if !folderExists {
return models.ErrDashboardFolderNotFound return isParentFolderChanged, models.ErrDashboardFolderNotFound
} }
} }
if !dashWithIdExists && !dashWithUidExists { if !dashWithIdExists && !dashWithUidExists {
return nil return isParentFolderChanged, nil
} }
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id { if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
return models.ErrDashboardWithSameUIDExists return isParentFolderChanged, models.ErrDashboardWithSameUIDExists
} }
existing := existingById existing := existingById
@@ -632,84 +655,92 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *models.Validat
existing = existingByUid existing = existingByUid
if !dash.IsFolder { if !dash.IsFolder {
cmd.Result.IsParentFolderChanged = true isParentFolderChanged = true
} }
} }
if (existing.IsFolder && !dash.IsFolder) || if (existing.IsFolder && !dash.IsFolder) ||
(!existing.IsFolder && dash.IsFolder) { (!existing.IsFolder && dash.IsFolder) {
return models.ErrDashboardTypeMismatch return isParentFolderChanged, models.ErrDashboardTypeMismatch
} }
if !dash.IsFolder && dash.FolderId != existing.FolderId { if !dash.IsFolder && dash.FolderId != existing.FolderId {
cmd.Result.IsParentFolderChanged = true isParentFolderChanged = true
} }
// check for is someone else has written in between // check for is someone else has written in between
if dash.Version != existing.Version { if dash.Version != existing.Version {
if cmd.Overwrite { if overwrite {
dash.SetVersion(existing.Version) dash.SetVersion(existing.Version)
} else { } else {
return models.ErrDashboardVersionMismatch return isParentFolderChanged, models.ErrDashboardVersionMismatch
} }
} }
// do not allow plugin dashboard updates without overwrite flag // do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && !cmd.Overwrite { if existing.PluginId != "" && !overwrite {
return models.UpdatePluginDashboardError{PluginId: existing.PluginId} return isParentFolderChanged, models.UpdatePluginDashboardError{PluginId: existing.PluginId}
} }
return nil return isParentFolderChanged, nil
} }
func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *models.ValidateDashboardBeforeSaveCommand) error { func getExistingDashboardByTitleAndFolder(sess *DBSession, dash *models.Dashboard, overwrite,
dash := cmd.Dashboard isParentFolderChanged bool) (bool, error) {
var existing models.Dashboard var existing models.Dashboard
exists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug,
exists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existing) dialect.BooleanStr(true), dash.FolderId).Get(&existing)
if err != nil { if err != nil {
return err return isParentFolderChanged, fmt.Errorf("SQL query for existing dashboard by org ID or folder ID failed: %w", err)
} }
if exists && dash.Id != existing.Id { if exists && dash.Id != existing.Id {
if existing.IsFolder && !dash.IsFolder { if existing.IsFolder && !dash.IsFolder {
return models.ErrDashboardWithSameNameAsFolder return isParentFolderChanged, models.ErrDashboardWithSameNameAsFolder
} }
if !existing.IsFolder && dash.IsFolder { if !existing.IsFolder && dash.IsFolder {
return models.ErrDashboardFolderWithSameNameAsDashboard return isParentFolderChanged, models.ErrDashboardFolderWithSameNameAsDashboard
} }
if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) { if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) {
cmd.Result.IsParentFolderChanged = true isParentFolderChanged = true
} }
if cmd.Overwrite { if overwrite {
dash.SetId(existing.Id) dash.SetId(existing.Id)
dash.SetUid(existing.Uid) dash.SetUid(existing.Uid)
dash.SetVersion(existing.Version) dash.SetVersion(existing.Version)
} else { } else {
return models.ErrDashboardWithSameNameInFolderExists return isParentFolderChanged, models.ErrDashboardWithSameNameInFolderExists
} }
} }
return nil return isParentFolderChanged, nil
} }
func ValidateDashboardBeforeSave(cmd *models.ValidateDashboardBeforeSaveCommand) (err error) { func (ss *SQLStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{} isParentFolderChanged := false
err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
return inTransaction(func(sess *DBSession) error { var err error
if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil { isParentFolderChanged, err = getExistingDashboardByIdOrUidForUpdate(sess, dashboard, overwrite)
if err != nil {
return err return err
} }
if err = getExistingDashboardByTitleAndFolder(sess, cmd); err != nil { isParentFolderChanged, err = getExistingDashboardByTitleAndFolder(sess, dashboard, overwrite,
isParentFolderChanged)
if err != nil {
return err return err
} }
return nil return nil
}) })
if err != nil {
return false, err
}
return isParentFolderChanged, nil
} }
func HasEditPermissionInFolders(query *models.HasEditPermissionInFoldersQuery) error { func HasEditPermissionInFolders(query *models.HasEditPermissionInFoldersQuery) error {
@@ -719,7 +750,8 @@ func HasEditPermissionInFolders(query *models.HasEditPermissionInFoldersQuery) e
} }
builder := &SQLBuilder{} builder := &SQLBuilder{}
builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgId, dialect.BooleanStr(true)) builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?",
query.SignedInUser.OrgId, dialect.BooleanStr(true))
builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_EDIT) builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_EDIT)
type folderCount struct { type folderCount struct {
+9 -7
View File
@@ -1,24 +1,26 @@
package sqlstore package sqlstore
import ( import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
) )
func init() { func init() {
bus.AddHandler("sql", UpdateDashboardAcl)
bus.AddHandler("sql", GetDashboardAclInfoList) bus.AddHandler("sql", GetDashboardAclInfoList)
} }
func UpdateDashboardAcl(cmd *models.UpdateDashboardAclCommand) error { func (ss *SQLStore) UpdateDashboardACL(dashboardID int64, items []*models.DashboardAcl) error {
return inTransaction(func(sess *DBSession) error { return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
// delete existing items // delete existing items
_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", cmd.DashboardID) _, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", dashboardID)
if err != nil { if err != nil {
return err return fmt.Errorf("deleting from dashboard_acl failed: %w", err)
} }
for _, item := range cmd.Items { for _, item := range items {
if item.UserID == 0 && item.TeamID == 0 && (item.Role == nil || !item.Role.IsValid()) { if item.UserID == 0 && item.TeamID == 0 && (item.Role == nil || !item.Role.IsValid()) {
return models.ErrDashboardAclInfoMissing return models.ErrDashboardAclInfoMissing
} }
@@ -35,7 +37,7 @@ func UpdateDashboardAcl(cmd *models.UpdateDashboardAclCommand) error {
// Update dashboard HasAcl flag // Update dashboard HasAcl flag
dashboard := models.Dashboard{HasAcl: true} dashboard := models.Dashboard{HasAcl: true}
_, err = sess.Cols("has_acl").Where("id=?", cmd.DashboardID).Update(&dashboard) _, err = sess.Cols("has_acl").Where("id=?", dashboardID).Update(&dashboard)
return err return err
}) })
} }
+19 -22
View File
@@ -11,14 +11,15 @@ import (
func TestDashboardAclDataAccess(t *testing.T) { func TestDashboardAclDataAccess(t *testing.T) {
Convey("Testing DB", t, func() { Convey("Testing DB", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
Convey("Given a dashboard folder and a user", func() { Convey("Given a dashboard folder and a user", func() {
currentUser := createUser(t, "viewer", "Viewer", false) currentUser := createUser(t, sqlStore, "viewer", "Viewer", false)
savedFolder := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod", "webapp") savedFolder := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
childDash := insertTestDashboard(t, "2 test dash", 1, savedFolder.Id, false, "prod", "webapp") childDash := insertTestDashboard(t, sqlStore, "2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
Convey("When adding dashboard permission with userId and teamId set to 0", func() { Convey("When adding dashboard permission with userId and teamId set to 0", func() {
err := testHelperUpdateDashboardAcl(savedFolder.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1, OrgID: 1,
DashboardID: savedFolder.Id, DashboardID: savedFolder.Id,
Permission: models.PERMISSION_EDIT, Permission: models.PERMISSION_EDIT,
@@ -61,10 +62,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
}) })
Convey("Given dashboard folder with removed default permissions", func() { Convey("Given dashboard folder with removed default permissions", func() {
err := UpdateDashboardAcl(&models.UpdateDashboardAclCommand{ err := sqlStore.UpdateDashboardACL(savedFolder.Id, nil)
DashboardID: savedFolder.Id,
Items: []*models.DashboardAcl{},
})
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("When reading dashboard acl should return no acl items", func() { Convey("When reading dashboard acl should return no acl items", func() {
@@ -78,7 +76,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
}) })
Convey("Given dashboard folder permission", func() { Convey("Given dashboard folder permission", func() {
err := testHelperUpdateDashboardAcl(savedFolder.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1, OrgID: 1,
UserID: currentUser.Id, UserID: currentUser.Id,
DashboardID: savedFolder.Id, DashboardID: savedFolder.Id,
@@ -97,7 +95,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
}) })
Convey("Given child dashboard permission", func() { Convey("Given child dashboard permission", func() {
err := testHelperUpdateDashboardAcl(childDash.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, childDash.Id, models.DashboardAcl{
OrgID: 1, OrgID: 1,
UserID: currentUser.Id, UserID: currentUser.Id,
DashboardID: childDash.Id, DashboardID: childDash.Id,
@@ -121,7 +119,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
}) })
Convey("Given child dashboard permission in folder with no permissions", func() { Convey("Given child dashboard permission in folder with no permissions", func() {
err := testHelperUpdateDashboardAcl(childDash.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, childDash.Id, models.DashboardAcl{
OrgID: 1, OrgID: 1,
UserID: currentUser.Id, UserID: currentUser.Id,
DashboardID: childDash.Id, DashboardID: childDash.Id,
@@ -149,7 +147,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
}) })
Convey("Should be able to add dashboard permission", func() { Convey("Should be able to add dashboard permission", func() {
err := testHelperUpdateDashboardAcl(savedFolder.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1, OrgID: 1,
UserID: currentUser.Id, UserID: currentUser.Id,
DashboardID: savedFolder.Id, DashboardID: savedFolder.Id,
@@ -169,7 +167,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email) So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email)
Convey("Should be able to delete an existing permission", func() { Convey("Should be able to delete an existing permission", func() {
err := testHelperUpdateDashboardAcl(savedFolder.Id) err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id)
So(err, ShouldBeNil) So(err, ShouldBeNil)
q3 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1} q3 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1}
@@ -180,14 +178,13 @@ func TestDashboardAclDataAccess(t *testing.T) {
}) })
Convey("Given a team", func() { Convey("Given a team", func() {
group1 := models.CreateTeamCommand{Name: "group1 name", OrgId: 1} team1, err := sqlStore.CreateTeam("group1 name", "", 1)
err := CreateTeam(&group1)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("Should be able to add a user permission for a team", func() { Convey("Should be able to add a user permission for a team", func() {
err := testHelperUpdateDashboardAcl(savedFolder.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1, OrgID: 1,
TeamID: group1.Result.Id, TeamID: team1.Id,
DashboardID: savedFolder.Id, DashboardID: savedFolder.Id,
Permission: models.PERMISSION_EDIT, Permission: models.PERMISSION_EDIT,
}) })
@@ -198,13 +195,13 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id) So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
So(q1.Result[0].Permission, ShouldEqual, models.PERMISSION_EDIT) So(q1.Result[0].Permission, ShouldEqual, models.PERMISSION_EDIT)
So(q1.Result[0].TeamId, ShouldEqual, group1.Result.Id) So(q1.Result[0].TeamId, ShouldEqual, team1.Id)
}) })
Convey("Should be able to update an existing permission for a team", func() { Convey("Should be able to update an existing permission for a team", func() {
err := testHelperUpdateDashboardAcl(savedFolder.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1, OrgID: 1,
TeamID: group1.Result.Id, TeamID: team1.Id,
DashboardID: savedFolder.Id, DashboardID: savedFolder.Id,
Permission: models.PERMISSION_ADMIN, Permission: models.PERMISSION_ADMIN,
}) })
@@ -216,7 +213,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(len(q3.Result), ShouldEqual, 1) So(len(q3.Result), ShouldEqual, 1)
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id) So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
So(q3.Result[0].Permission, ShouldEqual, models.PERMISSION_ADMIN) So(q3.Result[0].Permission, ShouldEqual, models.PERMISSION_ADMIN)
So(q3.Result[0].TeamId, ShouldEqual, group1.Result.Id) So(q3.Result[0].TeamId, ShouldEqual, team1.Id)
}) })
}) })
}) })
+59 -34
View File
@@ -7,21 +7,22 @@ import (
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"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/search" "github.com/grafana/grafana/pkg/services/search"
) )
func TestDashboardFolderDataAccess(t *testing.T) { func TestDashboardFolderDataAccess(t *testing.T) {
Convey("Testing DB", t, func() { Convey("Testing DB", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
Convey("Given one dashboard folder with two dashboards and one dashboard in the root folder", func() { Convey("Given one dashboard folder with two dashboards and one dashboard in the root folder", func() {
folder := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod", "webapp") folder := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
dashInRoot := insertTestDashboard(t, "test dash 67", 1, 0, false, "prod", "webapp") dashInRoot := insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod", "webapp")
childDash := insertTestDashboard(t, "test dash 23", 1, folder.Id, false, "prod", "webapp") childDash := insertTestDashboard(t, sqlStore, "test dash 23", 1, folder.Id, false, "prod", "webapp")
insertTestDashboard(t, "test dash 45", 1, folder.Id, false, "prod") insertTestDashboard(t, sqlStore, "test dash 45", 1, folder.Id, false, "prod")
currentUser := createUser(t, "viewer", "Viewer", false) currentUser := createUser(t, sqlStore, "viewer", "Viewer", false)
Convey("and no acls are set", func() { Convey("and no acls are set", func() {
Convey("should return all dashboards", func() { Convey("should return all dashboards", func() {
@@ -40,7 +41,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
Convey("and acl is set for dashboard folder", func() { Convey("and acl is set for dashboard folder", func() {
var otherUser int64 = 999 var otherUser int64 = 999
err := testHelperUpdateDashboardAcl(folder.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, folder.Id, models.DashboardAcl{
DashboardID: folder.Id, DashboardID: folder.Id,
OrgID: 1, OrgID: 1,
UserID: otherUser, UserID: otherUser,
@@ -61,7 +62,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("when the user is given permission", func() { Convey("when the user is given permission", func() {
err := testHelperUpdateDashboardAcl(folder.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, folder.Id, models.DashboardAcl{
DashboardID: folder.Id, OrgID: 1, UserID: currentUser.Id, Permission: models.PERMISSION_EDIT, DashboardID: folder.Id, OrgID: 1, UserID: currentUser.Id, Permission: models.PERMISSION_EDIT,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -102,9 +103,9 @@ func TestDashboardFolderDataAccess(t *testing.T) {
Convey("and acl is set for dashboard child and folder has all permissions removed", func() { Convey("and acl is set for dashboard child and folder has all permissions removed", func() {
var otherUser int64 = 999 var otherUser int64 = 999
err := testHelperUpdateDashboardAcl(folder.Id) err := testHelperUpdateDashboardAcl(t, sqlStore, folder.Id)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = testHelperUpdateDashboardAcl(childDash.Id, models.DashboardAcl{ err = testHelperUpdateDashboardAcl(t, sqlStore, childDash.Id, models.DashboardAcl{
DashboardID: folder.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT, DashboardID: folder.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -120,7 +121,9 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("when the user is given permission to child", func() { Convey("when the user is given permission to child", func() {
err := testHelperUpdateDashboardAcl(childDash.Id, models.DashboardAcl{DashboardID: childDash.Id, OrgID: 1, UserID: currentUser.Id, Permission: models.PERMISSION_EDIT}) err := testHelperUpdateDashboardAcl(t, sqlStore, childDash.Id, models.DashboardAcl{
DashboardID: childDash.Id, OrgID: 1, UserID: currentUser.Id, Permission: models.PERMISSION_EDIT,
})
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("should be able to search for child dashboard but not folder", func() { Convey("should be able to search for child dashboard but not folder", func() {
@@ -156,18 +159,24 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("Given two dashboard folders with one dashboard each and one dashboard in the root folder", func() { Convey("Given two dashboard folders with one dashboard each and one dashboard in the root folder", func() {
folder1 := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod") folder1 := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod")
folder2 := insertTestDashboard(t, "2 test dash folder", 1, 0, true, "prod") folder2 := insertTestDashboard(t, sqlStore, "2 test dash folder", 1, 0, true, "prod")
dashInRoot := insertTestDashboard(t, "test dash 67", 1, 0, false, "prod") dashInRoot := insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod")
childDash1 := insertTestDashboard(t, "child dash 1", 1, folder1.Id, false, "prod") childDash1 := insertTestDashboard(t, sqlStore, "child dash 1", 1, folder1.Id, false, "prod")
childDash2 := insertTestDashboard(t, "child dash 2", 1, folder2.Id, false, "prod") childDash2 := insertTestDashboard(t, sqlStore, "child dash 2", 1, folder2.Id, false, "prod")
currentUser := createUser(t, "viewer", "Viewer", false) currentUser := createUser(t, sqlStore, "viewer", "Viewer", false)
var rootFolderId int64 = 0 var rootFolderId int64 = 0
Convey("and one folder is expanded, the other collapsed", func() { Convey("and one folder is expanded, the other collapsed", func() {
Convey("should return dashboards in root and expanded folder", func() { Convey("should return dashboards in root and expanded folder", func() {
query := &search.FindPersistedDashboardsQuery{FolderIds: []int64{rootFolderId, folder1.Id}, SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER}, OrgId: 1} query := &search.FindPersistedDashboardsQuery{
FolderIds: []int64{
rootFolderId, folder1.Id}, SignedInUser: &models.SignedInUser{UserId: currentUser.Id,
OrgId: 1, OrgRole: models.ROLE_VIEWER,
},
OrgId: 1,
}
err := SearchDashboards(query) err := SearchDashboards(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 4) So(len(query.Result), ShouldEqual, 4)
@@ -179,14 +188,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("and acl is set for one dashboard folder", func() { Convey("and acl is set for one dashboard folder", func() {
var otherUser int64 = 999 const otherUser int64 = 999
err := testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT, DashboardID: folder1.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("and a dashboard is moved from folder without acl to the folder with an acl", func() { Convey("and a dashboard is moved from folder without acl to the folder with an acl", func() {
moveDashboard(1, childDash2.Data, folder1.Id) moveDashboard(t, sqlStore, 1, childDash2.Data, folder1.Id)
Convey("should not return folder with acl or its children", func() { Convey("should not return folder with acl or its children", func() {
query := &search.FindPersistedDashboardsQuery{ query := &search.FindPersistedDashboardsQuery{
@@ -201,7 +210,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
}) })
Convey("and a dashboard is moved from folder with acl to the folder without an acl", func() { Convey("and a dashboard is moved from folder with acl to the folder without an acl", func() {
moveDashboard(1, childDash1.Data, folder2.Id) moveDashboard(t, sqlStore, 1, childDash1.Data, folder2.Id)
Convey("should return folder without acl and its children", func() { Convey("should return folder without acl and its children", func() {
query := &search.FindPersistedDashboardsQuery{ query := &search.FindPersistedDashboardsQuery{
@@ -220,12 +229,12 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("and a dashboard with an acl is moved to the folder without an acl", func() { Convey("and a dashboard with an acl is moved to the folder without an acl", func() {
err := testHelperUpdateDashboardAcl(childDash1.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, childDash1.Id, models.DashboardAcl{
DashboardID: childDash1.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT, DashboardID: childDash1.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
moveDashboard(1, childDash1.Data, folder2.Id) moveDashboard(t, sqlStore, 1, childDash1.Data, folder2.Id)
Convey("should return folder without acl but not the dashboard with acl", func() { Convey("should return folder without acl but not the dashboard with acl", func() {
query := &search.FindPersistedDashboardsQuery{ query := &search.FindPersistedDashboardsQuery{
@@ -246,13 +255,13 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("Given two dashboard folders", func() { Convey("Given two dashboard folders", func() {
folder1 := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod") folder1 := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod")
folder2 := insertTestDashboard(t, "2 test dash folder", 1, 0, true, "prod") folder2 := insertTestDashboard(t, sqlStore, "2 test dash folder", 1, 0, true, "prod")
insertTestDashboard(t, "folder in another org", 2, 0, true, "prod") insertTestDashboard(t, sqlStore, "folder in another org", 2, 0, true, "prod")
adminUser := createUser(t, "admin", "Admin", true) adminUser := createUser(t, sqlStore, "admin", "Admin", true)
editorUser := createUser(t, "editor", "Editor", false) editorUser := createUser(t, sqlStore, "editor", "Editor", false)
viewerUser := createUser(t, "viewer", "Viewer", false) viewerUser := createUser(t, sqlStore, "viewer", "Viewer", false)
Convey("Admin users", func() { Convey("Admin users", func() {
Convey("Should have write access to all dashboard folders in their org", func() { Convey("Should have write access to all dashboard folders in their org", func() {
@@ -343,7 +352,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() { Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() {
err := testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: editorUser.Id, Permission: models.PERMISSION_VIEW, DashboardID: folder1.Id, OrgID: 1, UserID: editorUser.Id, Permission: models.PERMISSION_VIEW,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -407,7 +416,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() { Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() {
err := testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_EDIT, DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_EDIT,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -438,7 +447,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("and admin permission is given for user with org role viewer in one dashboard folder", func() { Convey("and admin permission is given for user with org role viewer in one dashboard folder", func() {
err := testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_ADMIN, DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_ADMIN,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -454,7 +463,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
Convey("and edit permission is given for user with org role viewer in one dashboard folder", func() { Convey("and edit permission is given for user with org role viewer in one dashboard folder", func() {
err := testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{ err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_EDIT, DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_EDIT,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -472,3 +481,19 @@ func TestDashboardFolderDataAccess(t *testing.T) {
}) })
}) })
} }
func moveDashboard(t *testing.T, sqlStore *SQLStore, orgId int64, dashboard *simplejson.Json,
newFolderId int64) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: newFolderId,
Dashboard: dashboard,
Overwrite: true,
}
dash, err := sqlStore.SaveDashboard(cmd)
So(err, ShouldBeNil)
return dash
}
+27 -31
View File
@@ -1,6 +1,7 @@
package sqlstore package sqlstore
import ( import (
"context"
"errors" "errors"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
@@ -8,9 +9,6 @@ import (
) )
func init() { func init() {
bus.AddHandler("sql", GetProvisionedDashboardDataQuery)
bus.AddHandler("sql", SaveProvisionedDashboard)
bus.AddHandler("sql", GetProvisionedDataByDashboardId)
bus.AddHandler("sql", UnprovisionDashboard) bus.AddHandler("sql", UnprovisionDashboard)
bus.AddHandler("sql", DeleteOrphanedProvisionedDashboards) bus.AddHandler("sql", DeleteOrphanedProvisionedDashboards)
} }
@@ -22,64 +20,62 @@ type DashboardExtras struct {
Value string Value string
} }
func GetProvisionedDataByDashboardId(cmd *models.GetProvisionedDashboardDataByIdQuery) error { func (ss *SQLStore) GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
result := &models.DashboardProvisioning{} var data models.DashboardProvisioning
exists, err := x.Where("dashboard_id = ?", dashboardID).Get(&data)
exist, err := x.Where("dashboard_id = ?", cmd.DashboardId).Get(result)
if err != nil { if err != nil {
return err return nil, err
} }
if exist { if exists {
cmd.Result = result return &data, nil
} }
return nil return nil, nil
} }
func SaveProvisionedDashboard(cmd *models.SaveProvisionedDashboardCommand) error { func (ss *SQLStore) SaveProvisionedDashboard(cmd models.SaveDashboardCommand,
return inTransaction(func(sess *DBSession) error { provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
err := saveDashboard(sess, cmd.DashboardCmd) err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
if err != nil { if err := saveDashboard(sess, &cmd); err != nil {
return err return err
} }
cmd.Result = cmd.DashboardCmd.Result if provisioning.Updated == 0 {
if cmd.DashboardProvisioning.Updated == 0 { provisioning.Updated = cmd.Result.Updated.Unix()
cmd.DashboardProvisioning.Updated = cmd.Result.Updated.Unix()
} }
return saveProvisionedData(sess, cmd.DashboardProvisioning, cmd.Result) return saveProvisionedData(sess, provisioning, cmd.Result)
}) })
return cmd.Result, err
} }
func saveProvisionedData(sess *DBSession, cmd *models.DashboardProvisioning, dashboard *models.Dashboard) error { func saveProvisionedData(sess *DBSession, provisioning *models.DashboardProvisioning, dashboard *models.Dashboard) error {
result := &models.DashboardProvisioning{} result := &models.DashboardProvisioning{}
exist, err := sess.Where("dashboard_id=? AND name = ?", dashboard.Id, cmd.Name).Get(result) exist, err := sess.Where("dashboard_id=? AND name = ?", dashboard.Id, provisioning.Name).Get(result)
if err != nil { if err != nil {
return err return err
} }
cmd.Id = result.Id provisioning.Id = result.Id
cmd.DashboardId = dashboard.Id provisioning.DashboardId = dashboard.Id
if exist { if exist {
_, err = sess.ID(result.Id).Update(cmd) _, err = sess.ID(result.Id).Update(provisioning)
} else { } else {
_, err = sess.Insert(cmd) _, err = sess.Insert(provisioning)
} }
return err return err
} }
func GetProvisionedDashboardDataQuery(cmd *models.GetProvisionedDashboardDataQuery) error { func (ss *SQLStore) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
var result []*models.DashboardProvisioning var result []*models.DashboardProvisioning
if err := ss.engine.Where("name = ?", name).Find(&result); err != nil {
if err := x.Where("name = ?", cmd.Name).Find(&result); err != nil { return nil, err
return err
} }
cmd.Result = result return result, nil
return nil
} }
// UnprovisionDashboard removes row in dashboard_provisioning for the dashboard making it seem as if manually created. // UnprovisionDashboard removes row in dashboard_provisioning for the dashboard making it seem as if manually created.
@@ -13,9 +13,9 @@ import (
func TestDashboardProvisioningTest(t *testing.T) { func TestDashboardProvisioningTest(t *testing.T) {
Convey("Testing Dashboard provisioning", t, func() { Convey("Testing Dashboard provisioning", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
folderCmd := &models.SaveDashboardCommand{ folderCmd := models.SaveDashboardCommand{
OrgId: 1, OrgId: 1,
FolderId: 0, FolderId: 0,
IsFolder: true, IsFolder: true,
@@ -25,13 +25,13 @@ func TestDashboardProvisioningTest(t *testing.T) {
}), }),
} }
err := SaveDashboard(folderCmd) dash, err := sqlStore.SaveDashboard(folderCmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
saveDashboardCmd := &models.SaveDashboardCommand{ saveDashboardCmd := models.SaveDashboardCommand{
OrgId: 1, OrgId: 1,
IsFolder: false, IsFolder: false,
FolderId: folderCmd.Result.Id, FolderId: dash.Id,
Dashboard: simplejson.NewFromAny(map[string]interface{}{ Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil, "id": nil,
"title": "test dashboard", "title": "test dashboard",
@@ -41,43 +41,38 @@ func TestDashboardProvisioningTest(t *testing.T) {
Convey("Saving dashboards with provisioning meta data", func() { Convey("Saving dashboards with provisioning meta data", func() {
now := time.Now() now := time.Now()
cmd := &models.SaveProvisionedDashboardCommand{ provisioning := &models.DashboardProvisioning{
DashboardCmd: saveDashboardCmd, Name: "default",
DashboardProvisioning: &models.DashboardProvisioning{ ExternalId: "/var/grafana.json",
Name: "default", Updated: now.Unix(),
ExternalId: "/var/grafana.json",
Updated: now.Unix(),
},
} }
err := SaveProvisionedDashboard(cmd) dash, err := sqlStore.SaveProvisionedDashboard(saveDashboardCmd, provisioning)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(cmd.Result, ShouldNotBeNil) So(dash, ShouldNotBeNil)
So(cmd.Result.Id, ShouldNotEqual, 0) So(dash.Id, ShouldNotEqual, 0)
dashId := cmd.Result.Id dashId := dash.Id
Convey("Deleting orphaned provisioned dashboards", func() { Convey("Deleting orphaned provisioned dashboards", func() {
anotherCmd := &models.SaveProvisionedDashboardCommand{ saveCmd := models.SaveDashboardCommand{
DashboardCmd: &models.SaveDashboardCommand{ OrgId: 1,
OrgId: 1, IsFolder: false,
IsFolder: false, FolderId: dash.Id,
FolderId: folderCmd.Result.Id, Dashboard: simplejson.NewFromAny(map[string]interface{}{
Dashboard: simplejson.NewFromAny(map[string]interface{}{ "id": nil,
"id": nil, "title": "another_dashboard",
"title": "another_dashboard", }),
}), }
}, provisioning := &models.DashboardProvisioning{
DashboardProvisioning: &models.DashboardProvisioning{ Name: "another_reader",
Name: "another_reader", ExternalId: "/var/grafana.json",
ExternalId: "/var/grafana.json", Updated: now.Unix(),
Updated: now.Unix(),
},
} }
err := SaveProvisionedDashboard(anotherCmd) anotherDash, err := sqlStore.SaveProvisionedDashboard(saveCmd, provisioning)
So(err, ShouldBeNil) So(err, ShouldBeNil)
query := &models.GetDashboardsQuery{DashboardIds: []int64{anotherCmd.Result.Id}} query := &models.GetDashboardsQuery{DashboardIds: []int64{anotherDash.Id}}
err = GetDashboards(query) err = GetDashboards(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil) So(query.Result, ShouldNotBeNil)
@@ -85,7 +80,7 @@ func TestDashboardProvisioningTest(t *testing.T) {
deleteCmd := &models.DeleteOrphanedProvisionedDashboardsCommand{ReaderNames: []string{"default"}} deleteCmd := &models.DeleteOrphanedProvisionedDashboardsCommand{ReaderNames: []string{"default"}}
So(DeleteOrphanedProvisionedDashboards(deleteCmd), ShouldBeNil) So(DeleteOrphanedProvisionedDashboards(deleteCmd), ShouldBeNil)
query = &models.GetDashboardsQuery{DashboardIds: []int64{cmd.Result.Id, anotherCmd.Result.Id}} query = &models.GetDashboardsQuery{DashboardIds: []int64{dash.Id, anotherDash.Id}}
err = GetDashboards(query) err = GetDashboards(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -94,45 +89,38 @@ func TestDashboardProvisioningTest(t *testing.T) {
}) })
Convey("Can query for provisioned dashboards", func() { Convey("Can query for provisioned dashboards", func() {
query := &models.GetProvisionedDashboardDataQuery{Name: "default"} rslt, err := sqlStore.GetProvisionedDashboardData("default")
err := GetProvisionedDashboardDataQuery(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1) So(len(rslt), ShouldEqual, 1)
So(query.Result[0].DashboardId, ShouldEqual, dashId) So(rslt[0].DashboardId, ShouldEqual, dashId)
So(query.Result[0].Updated, ShouldEqual, now.Unix()) So(rslt[0].Updated, ShouldEqual, now.Unix())
}) })
Convey("Can query for one provisioned dashboard", func() { Convey("Can query for one provisioned dashboard", func() {
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: cmd.Result.Id} data, err := sqlStore.GetProvisionedDataByDashboardID(dash.Id)
err := GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil) So(data, ShouldNotBeNil)
}) })
Convey("Can query for none provisioned dashboard", func() { Convey("Can query for none provisioned dashboard", func() {
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: 3000} data, err := sqlStore.GetProvisionedDataByDashboardID(3000)
err := GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Result, ShouldBeNil) So(data, ShouldBeNil)
}) })
Convey("Deleting folder should delete provision meta data", func() { Convey("Deleting folder should delete provision meta data", func() {
deleteCmd := &models.DeleteDashboardCommand{ deleteCmd := &models.DeleteDashboardCommand{
Id: folderCmd.Result.Id, Id: dash.Id,
OrgId: 1, OrgId: 1,
} }
So(DeleteDashboard(deleteCmd), ShouldBeNil) So(DeleteDashboard(deleteCmd), ShouldBeNil)
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: cmd.Result.Id} data, err := sqlStore.GetProvisionedDataByDashboardID(dash.Id)
err = GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Result, ShouldBeNil) So(data, ShouldBeNil)
}) })
Convey("UnprovisionDashboard should delete provisioning metadata", func() { Convey("UnprovisionDashboard should delete provisioning metadata", func() {
@@ -142,11 +130,9 @@ func TestDashboardProvisioningTest(t *testing.T) {
So(UnprovisionDashboard(unprovisionCmd), ShouldBeNil) So(UnprovisionDashboard(unprovisionCmd), ShouldBeNil)
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: dashId} data, err := sqlStore.GetProvisionedDataByDashboardID(dashId)
err = GetProvisionedDataByDashboardId(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Result, ShouldBeNil) So(data, ShouldBeNil)
}) })
}) })
}) })
File diff suppressed because it is too large Load Diff
+42 -57
View File
@@ -22,13 +22,13 @@ import (
func TestDashboardDataAccess(t *testing.T) { func TestDashboardDataAccess(t *testing.T) {
Convey("Testing DB", t, func() { Convey("Testing DB", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
Convey("Given saved dashboard", func() { Convey("Given saved dashboard", func() {
savedFolder := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod", "webapp") savedFolder := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
savedDash := insertTestDashboard(t, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp") savedDash := insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
insertTestDashboard(t, "test dash 45", 1, savedFolder.Id, false, "prod") insertTestDashboard(t, sqlStore, "test dash 45", 1, savedFolder.Id, false, "prod")
insertTestDashboard(t, "test dash 67", 1, 0, false, "prod", "webapp") insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod", "webapp")
Convey("Should return dashboard model", func() { Convey("Should return dashboard model", func() {
So(savedDash.Title, ShouldEqual, "test dash 23") So(savedDash.Title, ShouldEqual, "test dash 23")
@@ -104,7 +104,7 @@ func TestDashboardDataAccess(t *testing.T) {
}) })
Convey("Should be able to delete dashboard", func() { Convey("Should be able to delete dashboard", func() {
dash := insertTestDashboard(t, "delete me", 1, 0, false, "delete this") dash := insertTestDashboard(t, sqlStore, "delete me", 1, 0, false, "delete this")
err := DeleteDashboard(&models.DeleteDashboardCommand{ err := DeleteDashboard(&models.DeleteDashboardCommand{
Id: dash.Id, Id: dash.Id,
@@ -129,8 +129,7 @@ func TestDashboardDataAccess(t *testing.T) {
"tags": []interface{}{}, "tags": []interface{}{},
}), }),
} }
_, err := sqlStore.SaveDashboard(cmd)
err := SaveDashboard(&cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
generateNewUid = util.GenerateShortUID generateNewUid = util.GenerateShortUID
@@ -145,13 +144,12 @@ func TestDashboardDataAccess(t *testing.T) {
}), }),
UserId: 100, UserId: 100,
} }
dashboard, err := sqlStore.SaveDashboard(cmd)
err := SaveDashboard(&cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(cmd.Result.CreatedBy, ShouldEqual, 100) So(dashboard.CreatedBy, ShouldEqual, 100)
So(cmd.Result.Created.IsZero(), ShouldBeFalse) So(dashboard.Created.IsZero(), ShouldBeFalse)
So(cmd.Result.UpdatedBy, ShouldEqual, 100) So(dashboard.UpdatedBy, ShouldEqual, 100)
So(cmd.Result.Updated.IsZero(), ShouldBeFalse) So(dashboard.Updated.IsZero(), ShouldBeFalse)
}) })
Convey("Should be able to update dashboard by id and remove folderId", func() { Convey("Should be able to update dashboard by id and remove folderId", func() {
@@ -166,10 +164,9 @@ func TestDashboardDataAccess(t *testing.T) {
FolderId: 2, FolderId: 2,
UserId: 100, UserId: 100,
} }
dash, err := sqlStore.SaveDashboard(cmd)
err := SaveDashboard(&cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(cmd.Result.FolderId, ShouldEqual, 2) So(dash.FolderId, ShouldEqual, 2)
cmd = models.SaveDashboardCommand{ cmd = models.SaveDashboardCommand{
OrgId: 1, OrgId: 1,
@@ -182,8 +179,7 @@ func TestDashboardDataAccess(t *testing.T) {
Overwrite: true, Overwrite: true,
UserId: 100, UserId: 100,
} }
_, err = sqlStore.SaveDashboard(cmd)
err = SaveDashboard(&cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
query := models.GetDashboardQuery{ query := models.GetDashboardQuery{
@@ -201,7 +197,7 @@ func TestDashboardDataAccess(t *testing.T) {
}) })
Convey("Should be able to delete empty folder", func() { Convey("Should be able to delete empty folder", func() {
emptyFolder := insertTestDashboard(t, "2 test dash folder", 1, 0, true, "prod", "webapp") emptyFolder := insertTestDashboard(t, sqlStore, "2 test dash folder", 1, 0, true, "prod", "webapp")
deleteCmd := &models.DeleteDashboardCommand{Id: emptyFolder.Id} deleteCmd := &models.DeleteDashboardCommand{Id: emptyFolder.Id}
err := DeleteDashboard(deleteCmd) err := DeleteDashboard(deleteCmd)
@@ -236,7 +232,7 @@ func TestDashboardDataAccess(t *testing.T) {
}), }),
} }
err := SaveDashboard(&cmd) _, err := sqlStore.SaveDashboard(cmd)
So(err, ShouldEqual, models.ErrDashboardNotFound) So(err, ShouldEqual, models.ErrDashboardNotFound)
}) })
@@ -250,8 +246,7 @@ func TestDashboardDataAccess(t *testing.T) {
"tags": []interface{}{}, "tags": []interface{}{},
}), }),
} }
_, err := sqlStore.SaveDashboard(cmd)
err := SaveDashboard(&cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@@ -366,7 +361,7 @@ func TestDashboardDataAccess(t *testing.T) {
}) })
Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() { Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
starredDash := insertTestDashboard(t, "starred dash", 1, 0, false) starredDash := insertTestDashboard(t, sqlStore, "starred dash", 1, 0, false)
err := StarDashboard(&models.StarDashboardCommand{ err := StarDashboard(&models.StarDashboardCommand{
DashboardId: starredDash.Id, DashboardId: starredDash.Id,
UserId: 10, UserId: 10,
@@ -396,9 +391,9 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Given a plugin with imported dashboards", func() { Convey("Given a plugin with imported dashboards", func() {
pluginId := "test-app" pluginId := "test-app"
appFolder := insertTestDashboardForPlugin("app-test", 1, 0, true, pluginId) appFolder := insertTestDashboardForPlugin(t, sqlStore, "app-test", 1, 0, true, pluginId)
insertTestDashboardForPlugin("app-dash1", 1, appFolder.Id, false, pluginId) insertTestDashboardForPlugin(t, sqlStore, "app-dash1", 1, appFolder.Id, false, pluginId)
insertTestDashboardForPlugin("app-dash2", 1, appFolder.Id, false, pluginId) insertTestDashboardForPlugin(t, sqlStore, "app-dash2", 1, appFolder.Id, false, pluginId)
Convey("Should return imported dashboard", func() { Convey("Should return imported dashboard", func() {
query := models.GetDashboardsByPluginIdQuery{ query := models.GetDashboardsByPluginIdQuery{
@@ -417,9 +412,9 @@ func TestDashboardDataAccess(t *testing.T) {
func TestDashboard_SortingOptions(t *testing.T) { func TestDashboard_SortingOptions(t *testing.T) {
// insertTestDashboard uses GoConvey's assertions. Workaround. // insertTestDashboard uses GoConvey's assertions. Workaround.
Convey("test with multiple sorting options", t, func() { Convey("test with multiple sorting options", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
dashB := insertTestDashboard(t, "Beta", 1, 0, false) dashB := insertTestDashboard(t, sqlStore, "Beta", 1, 0, false)
dashA := insertTestDashboard(t, "Alfa", 1, 0, false) dashA := insertTestDashboard(t, sqlStore, "Alfa", 1, 0, false)
assert.NotZero(t, dashA.Id) assert.NotZero(t, dashA.Id)
assert.Less(t, dashB.Id, dashA.Id) assert.Less(t, dashB.Id, dashA.Id)
@@ -441,7 +436,8 @@ func TestDashboard_SortingOptions(t *testing.T) {
}) })
} }
func insertTestDashboard(t *testing.T, title string, orgId int64, folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard { func insertTestDashboard(t *testing.T, sqlStore *SQLStore, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
t.Helper() t.Helper()
cmd := models.SaveDashboardCommand{ cmd := models.SaveDashboardCommand{
@@ -454,17 +450,20 @@ func insertTestDashboard(t *testing.T, title string, orgId int64, folderId int64
"tags": tags, "tags": tags,
}), }),
} }
dash, err := sqlStore.SaveDashboard(cmd)
err := SaveDashboard(&cmd)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, dash)
cmd.Result.Data.Set("id", cmd.Result.Id) dash.Data.Set("id", dash.Id)
cmd.Result.Data.Set("uid", cmd.Result.Uid) dash.Data.Set("uid", dash.Uid)
return cmd.Result return dash
} }
func insertTestDashboardForPlugin(title string, orgId int64, folderId int64, isFolder bool, pluginId string) *models.Dashboard { func insertTestDashboardForPlugin(t *testing.T, sqlStore *SQLStore, title string, orgId int64,
folderId int64, isFolder bool, pluginId string) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{ cmd := models.SaveDashboardCommand{
OrgId: orgId, OrgId: orgId,
FolderId: folderId, FolderId: folderId,
@@ -476,13 +475,13 @@ func insertTestDashboardForPlugin(title string, orgId int64, folderId int64, isF
PluginId: pluginId, PluginId: pluginId,
} }
err := SaveDashboard(&cmd) dash, err := sqlStore.SaveDashboard(cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
return cmd.Result return dash
} }
func createUser(t *testing.T, name string, role string, isAdmin bool) models.User { func createUser(t *testing.T, sqlStore *SQLStore, name string, role string, isAdmin bool) models.User {
t.Helper() t.Helper()
setting.AutoAssignOrg = true setting.AutoAssignOrg = true
@@ -490,27 +489,13 @@ func createUser(t *testing.T, name string, role string, isAdmin bool) models.Use
setting.AutoAssignOrgRole = role setting.AutoAssignOrgRole = role
currentUserCmd := models.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin} currentUserCmd := models.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin}
err := CreateUser(context.Background(), &currentUserCmd) currentUser, err := sqlStore.CreateUser(context.Background(), currentUserCmd)
require.NoError(t, err) require.NoError(t, err)
q1 := models.GetUserOrgListQuery{UserId: currentUserCmd.Result.Id} q1 := models.GetUserOrgListQuery{UserId: currentUser.Id}
err = GetUserOrgList(&q1) err = GetUserOrgList(&q1)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, models.RoleType(role), q1.Result[0].Role) require.Equal(t, models.RoleType(role), q1.Result[0].Role)
return currentUserCmd.Result return *currentUser
}
func moveDashboard(orgId int64, dashboard *simplejson.Json, newFolderId int64) *models.Dashboard {
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: newFolderId,
Dashboard: dashboard,
Overwrite: true,
}
err := SaveDashboard(&cmd)
So(err, ShouldBeNil)
return cmd.Result
} }
+13 -12
View File
@@ -13,7 +13,9 @@ import (
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
func updateTestDashboard(dashboard *models.Dashboard, data map[string]interface{}) { func updateTestDashboard(t *testing.T, sqlStore *SQLStore, dashboard *models.Dashboard, data map[string]interface{}) {
t.Helper()
data["id"] = dashboard.Id data["id"] = dashboard.Id
saveCmd := models.SaveDashboardCommand{ saveCmd := models.SaveDashboardCommand{
@@ -21,17 +23,16 @@ func updateTestDashboard(dashboard *models.Dashboard, data map[string]interface{
Overwrite: true, Overwrite: true,
Dashboard: simplejson.NewFromAny(data), Dashboard: simplejson.NewFromAny(data),
} }
_, err := sqlStore.SaveDashboard(saveCmd)
err := SaveDashboard(&saveCmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
} }
func TestGetDashboardVersion(t *testing.T) { func TestGetDashboardVersion(t *testing.T) {
Convey("Testing dashboard version retrieval", t, func() { Convey("Testing dashboard version retrieval", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
Convey("Get a Dashboard ID and version ID", func() { Convey("Get a Dashboard ID and version ID", func() {
savedDash := insertTestDashboard(t, "test dash 26", 1, 0, false, "diff") savedDash := insertTestDashboard(t, sqlStore, "test dash 26", 1, 0, false, "diff")
query := models.GetDashboardVersionQuery{ query := models.GetDashboardVersionQuery{
DashboardId: savedDash.Id, DashboardId: savedDash.Id,
@@ -71,8 +72,8 @@ func TestGetDashboardVersion(t *testing.T) {
func TestGetDashboardVersions(t *testing.T) { func TestGetDashboardVersions(t *testing.T) {
Convey("Testing dashboard versions retrieval", t, func() { Convey("Testing dashboard versions retrieval", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
savedDash := insertTestDashboard(t, "test dash 43", 1, 0, false, "diff-all") savedDash := insertTestDashboard(t, sqlStore, "test dash 43", 1, 0, false, "diff-all")
Convey("Get all versions for a given Dashboard ID", func() { Convey("Get all versions for a given Dashboard ID", func() {
query := models.GetDashboardVersionsQuery{DashboardId: savedDash.Id, OrgId: 1} query := models.GetDashboardVersionsQuery{DashboardId: savedDash.Id, OrgId: 1}
@@ -92,7 +93,7 @@ func TestGetDashboardVersions(t *testing.T) {
}) })
Convey("Get all versions for an updated dashboard", func() { Convey("Get all versions for an updated dashboard", func() {
updateTestDashboard(savedDash, map[string]interface{}{ updateTestDashboard(t, sqlStore, savedDash, map[string]interface{}{
"tags": "different-tag", "tags": "different-tag",
}) })
@@ -107,14 +108,14 @@ func TestGetDashboardVersions(t *testing.T) {
func TestDeleteExpiredVersions(t *testing.T) { func TestDeleteExpiredVersions(t *testing.T) {
Convey("Testing dashboard versions clean up", t, func() { Convey("Testing dashboard versions clean up", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
versionsToKeep := 5 versionsToKeep := 5
versionsToWrite := 10 versionsToWrite := 10
setting.DashboardVersionsToKeep = versionsToKeep setting.DashboardVersionsToKeep = versionsToKeep
savedDash := insertTestDashboard(t, "test dash 53", 1, 0, false, "diff-all") savedDash := insertTestDashboard(t, sqlStore, "test dash 53", 1, 0, false, "diff-all")
for i := 0; i < versionsToWrite-1; i++ { for i := 0; i < versionsToWrite-1; i++ {
updateTestDashboard(savedDash, map[string]interface{}{ updateTestDashboard(t, sqlStore, savedDash, map[string]interface{}{
"tags": "different-tag", "tags": "different-tag",
}) })
} }
@@ -152,7 +153,7 @@ func TestDeleteExpiredVersions(t *testing.T) {
versionsToWriteBigNumber := perBatch*maxBatches + versionsToWrite versionsToWriteBigNumber := perBatch*maxBatches + versionsToWrite
for i := 0; i < versionsToWriteBigNumber-versionsToWrite; i++ { for i := 0; i < versionsToWriteBigNumber-versionsToWrite; i++ {
updateTestDashboard(savedDash, map[string]interface{}{ updateTestDashboard(t, sqlStore, savedDash, map[string]interface{}{
"tags": "different-tag", "tags": "different-tag",
}) })
} }
+25 -22
View File
@@ -15,7 +15,7 @@ import (
func TestAccountDataAccess(t *testing.T) { func TestAccountDataAccess(t *testing.T) {
Convey("Testing Account DB Access", t, func() { Convey("Testing Account DB Access", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
Convey("Given we have organizations, we can query them by IDs", func() { Convey("Given we have organizations, we can query them by IDs", func() {
var err error var err error
@@ -78,13 +78,13 @@ func TestAccountDataAccess(t *testing.T) {
ac1cmd := models.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"} ac1cmd := models.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
ac2cmd := models.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"} ac2cmd := models.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
err := CreateUser(context.Background(), &ac1cmd) ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = CreateUser(context.Background(), &ac2cmd) ac2, err := sqlStore.CreateUser(context.Background(), ac2cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
q1 := models.GetUserOrgListQuery{UserId: ac1cmd.Result.Id} q1 := models.GetUserOrgListQuery{UserId: ac1.Id}
q2 := models.GetUserOrgListQuery{UserId: ac2cmd.Result.Id} q2 := models.GetUserOrgListQuery{UserId: ac2.Id}
err = GetUserOrgList(&q1) err = GetUserOrgList(&q1)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = GetUserOrgList(&q2) err = GetUserOrgList(&q2)
@@ -101,13 +101,10 @@ func TestAccountDataAccess(t *testing.T) {
ac1cmd := models.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"} ac1cmd := models.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
ac2cmd := models.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name", IsAdmin: true} ac2cmd := models.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name", IsAdmin: true}
err := CreateUser(context.Background(), &ac1cmd) ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
err = CreateUser(context.Background(), &ac2cmd) ac2, err := sqlStore.CreateUser(context.Background(), ac2cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
ac1 := ac1cmd.Result
ac2 := ac2cmd.Result
Convey("Should be able to read user info projection", func() { Convey("Should be able to read user info projection", func() {
query := models.GetUserProfileQuery{UserId: ac1.Id} query := models.GetUserProfileQuery{UserId: ac1.Id}
err = GetUserProfile(&query) err = GetUserProfile(&query)
@@ -266,9 +263,8 @@ func TestAccountDataAccess(t *testing.T) {
Convey("Given an org user with dashboard permissions", func() { Convey("Given an org user with dashboard permissions", func() {
ac3cmd := models.CreateUserCommand{Login: "ac3", Email: "ac3@test.com", Name: "ac3 name", IsAdmin: false} ac3cmd := models.CreateUserCommand{Login: "ac3", Email: "ac3@test.com", Name: "ac3 name", IsAdmin: false}
err := CreateUser(context.Background(), &ac3cmd) ac3, err := sqlStore.CreateUser(context.Background(), ac3cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
ac3 := ac3cmd.Result
orgUserCmd := models.AddOrgUserCommand{ orgUserCmd := models.AddOrgUserCommand{
OrgId: ac1.OrgId, OrgId: ac1.OrgId,
@@ -284,13 +280,17 @@ func TestAccountDataAccess(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 3) So(len(query.Result), ShouldEqual, 3)
dash1 := insertTestDashboard(t, "1 test dash", ac1.OrgId, 0, false, "prod", "webapp") dash1 := insertTestDashboard(t, sqlStore, "1 test dash", ac1.OrgId, 0, false, "prod", "webapp")
dash2 := insertTestDashboard(t, "2 test dash", ac3.OrgId, 0, false, "prod", "webapp") dash2 := insertTestDashboard(t, sqlStore, "2 test dash", ac3.OrgId, 0, false, "prod", "webapp")
err = testHelperUpdateDashboardAcl(dash1.Id, models.DashboardAcl{DashboardID: dash1.Id, OrgID: ac1.OrgId, UserID: ac3.Id, Permission: models.PERMISSION_EDIT}) err = testHelperUpdateDashboardAcl(t, sqlStore, dash1.Id, models.DashboardAcl{
DashboardID: dash1.Id, OrgID: ac1.OrgId, UserID: ac3.Id, Permission: models.PERMISSION_EDIT,
})
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = testHelperUpdateDashboardAcl(dash2.Id, models.DashboardAcl{DashboardID: dash2.Id, OrgID: ac3.OrgId, UserID: ac3.Id, Permission: models.PERMISSION_EDIT}) err = testHelperUpdateDashboardAcl(t, sqlStore, dash2.Id, models.DashboardAcl{
DashboardID: dash2.Id, OrgID: ac3.OrgId, UserID: ac3.Id, Permission: models.PERMISSION_EDIT,
})
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("When org user is deleted", func() { Convey("When org user is deleted", func() {
@@ -322,13 +322,16 @@ func TestAccountDataAccess(t *testing.T) {
}) })
} }
func testHelperUpdateDashboardAcl(dashboardId int64, items ...models.DashboardAcl) error { func testHelperUpdateDashboardAcl(t *testing.T, sqlStore *SQLStore, dashboardID int64,
cmd := models.UpdateDashboardAclCommand{DashboardID: dashboardId} items ...models.DashboardAcl) error {
for _, i := range items { t.Helper()
item := i
var itemPtrs []*models.DashboardAcl
for _, it := range items {
item := it
item.Created = time.Now() item.Created = time.Now()
item.Updated = time.Now() item.Updated = time.Now()
cmd.Items = append(cmd.Items, &item) itemPtrs = append(itemPtrs, &item)
} }
return UpdateDashboardAcl(&cmd) return sqlStore.UpdateDashboardACL(dashboardID, itemPtrs)
} }
+8 -6
View File
@@ -10,25 +10,27 @@ import (
) )
func init() { func init() {
bus.AddHandler("sql", GetPluginSettings)
bus.AddHandler("sql", GetPluginSettingById) bus.AddHandler("sql", GetPluginSettingById)
bus.AddHandler("sql", UpdatePluginSetting) bus.AddHandler("sql", UpdatePluginSetting)
bus.AddHandler("sql", UpdatePluginSettingVersion) bus.AddHandler("sql", UpdatePluginSettingVersion)
} }
func GetPluginSettings(query *models.GetPluginSettingsQuery) error { func (ss *SQLStore) GetPluginSettings(orgID int64) ([]*models.PluginSettingInfoDTO, error) {
sql := `SELECT org_id, plugin_id, enabled, pinned, plugin_version sql := `SELECT org_id, plugin_id, enabled, pinned, plugin_version
FROM plugin_setting ` FROM plugin_setting `
params := make([]interface{}, 0) params := make([]interface{}, 0)
if query.OrgId != 0 { if orgID != 0 {
sql += "WHERE org_id=?" sql += "WHERE org_id=?"
params = append(params, query.OrgId) params = append(params, orgID)
} }
sess := x.SQL(sql, params...) sess := x.SQL(sql, params...)
query.Result = make([]*models.PluginSettingInfoDTO, 0) var rslt []*models.PluginSettingInfoDTO
return sess.Find(&query.Result) if err := sess.Find(&rslt); err != nil {
return nil, err
}
return rslt, nil
} }
func GetPluginSettingById(query *models.GetPluginSettingByIdQuery) error { func GetPluginSettingById(query *models.GetPluginSettingByIdQuery) error {
@@ -5,7 +5,6 @@ package searchstore_test
import ( import (
"context" "context"
"fmt"
"testing" "testing"
"time" "time"
@@ -34,13 +33,11 @@ func TestBuilder_EqualResults_Basic(t *testing.T) {
} }
db := setupTestEnvironment(t) db := setupTestEnvironment(t)
err := createDashboards(0, 1, user.OrgId) createDashboards(t, db, 0, 1, user.OrgId)
require.NoError(t, err)
// create one dashboard in another organization that shouldn't // create one dashboard in another organization that shouldn't
// be listed in the results. // be listed in the results.
err = createDashboards(1, 2, 2) createDashboards(t, db, 1, 2, 2)
require.NoError(t, err)
builder := &searchstore.Builder{ builder := &searchstore.Builder{
Filters: []interface{}{ Filters: []interface{}{
@@ -51,7 +48,7 @@ func TestBuilder_EqualResults_Basic(t *testing.T) {
} }
res := []sqlstore.DashboardSearchProjection{} res := []sqlstore.DashboardSearchProjection{}
err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
sql, params := builder.ToSQL(limit, page) sql, params := builder.ToSQL(limit, page)
return sess.SQL(sql, params...).Find(&res) return sess.SQL(sql, params...).Find(&res)
}) })
@@ -77,8 +74,7 @@ func TestBuilder_Pagination(t *testing.T) {
} }
db := setupTestEnvironment(t) db := setupTestEnvironment(t)
err := createDashboards(0, 25, user.OrgId) createDashboards(t, db, 0, 25, user.OrgId)
require.NoError(t, err)
builder := &searchstore.Builder{ builder := &searchstore.Builder{
Filters: []interface{}{ Filters: []interface{}{
@@ -91,7 +87,7 @@ func TestBuilder_Pagination(t *testing.T) {
resPg1 := []sqlstore.DashboardSearchProjection{} resPg1 := []sqlstore.DashboardSearchProjection{}
resPg2 := []sqlstore.DashboardSearchProjection{} resPg2 := []sqlstore.DashboardSearchProjection{}
resPg3 := []sqlstore.DashboardSearchProjection{} resPg3 := []sqlstore.DashboardSearchProjection{}
err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
sql, params := builder.ToSQL(15, 1) sql, params := builder.ToSQL(15, 1)
err := sess.SQL(sql, params...).Find(&resPg1) err := sess.SQL(sql, params...).Find(&resPg1)
if err != nil { if err != nil {
@@ -124,8 +120,7 @@ func TestBuilder_Permissions(t *testing.T) {
} }
db := setupTestEnvironment(t) db := setupTestEnvironment(t)
err := createDashboards(0, 1, user.OrgId) createDashboards(t, db, 0, 1, user.OrgId)
require.NoError(t, err)
level := models.PERMISSION_EDIT level := models.PERMISSION_EDIT
@@ -145,7 +140,7 @@ func TestBuilder_Permissions(t *testing.T) {
} }
res := []sqlstore.DashboardSearchProjection{} res := []sqlstore.DashboardSearchProjection{}
err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
sql, params := builder.ToSQL(limit, page) sql, params := builder.ToSQL(limit, page)
return sess.SQL(sql, params...).Find(&res) return sess.SQL(sql, params...).Find(&res)
}) })
@@ -161,10 +156,10 @@ func setupTestEnvironment(t *testing.T) *sqlstore.SQLStore {
return store return store
} }
func createDashboards(startID, endID int, orgID int64) error { func createDashboards(t *testing.T, db *sqlstore.SQLStore, startID, endID int, orgID int64) {
if endID < startID { t.Helper()
return fmt.Errorf("startID must be smaller than endID")
} require.GreaterOrEqual(t, endID, startID)
for i := startID; i < endID; i++ { for i := startID; i < endID; i++ {
dashboard, err := simplejson.NewJson([]byte(`{ dashboard, err := simplejson.NewJson([]byte(`{
@@ -176,20 +171,15 @@ func createDashboards(startID, endID int, orgID int64) error {
"schemaVersion": 16, "schemaVersion": 16,
"version": 0 "version": 0
}`)) }`))
if err != nil { require.NoError(t, err)
return err _, err = db.SaveDashboard(models.SaveDashboardCommand{
}
err = sqlstore.SaveDashboard(&models.SaveDashboardCommand{
Dashboard: dashboard, Dashboard: dashboard,
UserId: 1, UserId: 1,
OrgId: orgID, OrgId: orgID,
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
}) })
if err != nil { require.NoError(t, err)
return err
}
} }
return nil
} }
// lexiCounter counts in a lexicographically sortable order. // lexiCounter counts in a lexicographically sortable order.
+66 -85
View File
@@ -12,6 +12,7 @@ import (
"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/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestSQLBuilder(t *testing.T) { func TestSQLBuilder(t *testing.T) {
@@ -138,8 +139,8 @@ func TestSQLBuilder(t *testing.T) {
}) })
} }
var shouldFind = true const shouldFind = true
var shouldNotFind = false const shouldNotFind = false
type DashboardProps struct { type DashboardProps struct {
OrgId int64 OrgId int64
@@ -164,38 +165,35 @@ type dashboardResponse struct {
} }
func test(t *testing.T, dashboardProps DashboardProps, dashboardPermission *DashboardPermission, search Search, shouldFind bool) { func test(t *testing.T, dashboardProps DashboardProps, dashboardPermission *DashboardPermission, search Search, shouldFind bool) {
// Will also cleanup the db t.Helper()
sqlStore := InitTestDB(t)
dashboard, err := createDummyDashboard(dashboardProps) t.Run("", func(t *testing.T) {
if !assert.Equal(t, nil, err) { // Will also cleanup the db
return sqlStore := InitTestDB(t)
}
var aclUserId int64 dashboard := createDummyDashboard(t, sqlStore, dashboardProps)
if dashboardPermission != nil {
aclUserId, err = createDummyAcl(dashboardPermission, search, dashboard.Id) var aclUserID int64
if !assert.Equal(t, nil, err) { if dashboardPermission != nil {
return aclUserID = createDummyACL(t, sqlStore, dashboardPermission, search, dashboard.Id)
t.Logf("Created ACL with user ID %d\n", aclUserID)
} }
} dashboards := getDashboards(t, sqlStore, search, aclUserID)
dashboards, err := getDashboards(sqlStore, search, aclUserId)
if !assert.Equal(t, nil, err) {
return
}
if shouldFind { if shouldFind {
if assert.Equal(t, 1, len(dashboards), "Should return one dashboard") { require.Len(t, dashboards, 1, "Should return one dashboard")
assert.Equal(t, dashboards[0].Id, dashboard.Id, "Should return created dashboard") assert.Equal(t, dashboard.Id, dashboards[0].Id, "Should return created dashboard")
} else {
assert.Empty(t, dashboards, "Should not return any dashboard")
} }
} else { })
assert.Equal(t, 0, len(dashboards), "Should node return any dashboard")
}
} }
func createDummyUser() (*models.User, error) { func createDummyUser(t *testing.T, sqlStore *SQLStore) *models.User {
t.Helper()
uid := strconv.Itoa(rand.Intn(9999999)) uid := strconv.Itoa(rand.Intn(9999999))
createUserCmd := &models.CreateUserCommand{ createUserCmd := models.CreateUserCommand{
Email: uid + "@example.com", Email: uid + "@example.com",
Login: uid, Login: uid,
Name: uid, Name: uid,
@@ -207,33 +205,28 @@ func createDummyUser() (*models.User, error) {
SkipOrgSetup: false, SkipOrgSetup: false,
DefaultOrgRole: string(models.ROLE_VIEWER), DefaultOrgRole: string(models.ROLE_VIEWER),
} }
err := CreateUser(context.Background(), createUserCmd) user, err := sqlStore.CreateUser(context.Background(), createUserCmd)
if err != nil { require.NoError(t, err)
return nil, err
}
return &createUserCmd.Result, nil return user
} }
func createDummyTeam() (*models.Team, error) { func createDummyTeam(t *testing.T, sqlStore *SQLStore) models.Team {
cmd := &models.CreateTeamCommand{ t.Helper()
// Does not matter in this tests actually
OrgId: 1,
Name: "test",
Email: "test@example.com",
}
err := CreateTeam(cmd)
if err != nil {
return nil, err
}
return &cmd.Result, nil team, err := sqlStore.CreateTeam("test", "test@example.com", 1)
require.NoError(t, err)
return team
} }
func createDummyDashboard(dashboardProps DashboardProps) (*models.Dashboard, error) { func createDummyDashboard(t *testing.T, sqlStore *SQLStore, dashboardProps DashboardProps) *models.Dashboard {
json, _ := simplejson.NewJson([]byte(`{"schemaVersion":17,"title":"gdev dashboards","uid":"","version":1}`)) t.Helper()
saveDashboardCmd := &models.SaveDashboardCommand{ json, err := simplejson.NewJson([]byte(`{"schemaVersion":17,"title":"gdev dashboards","uid":"","version":1}`))
require.NoError(t, err)
saveDashboardCmd := models.SaveDashboardCommand{
Dashboard: json, Dashboard: json,
UserId: 0, UserId: 0,
Overwrite: false, Overwrite: false,
@@ -250,53 +243,40 @@ func createDummyDashboard(dashboardProps DashboardProps) (*models.Dashboard, err
saveDashboardCmd.OrgId = 1 saveDashboardCmd.OrgId = 1
} }
err := SaveDashboard(saveDashboardCmd) dash, err := sqlStore.SaveDashboard(saveDashboardCmd)
if err != nil { require.NoError(t, err)
return nil, err
}
return saveDashboardCmd.Result, nil t.Logf("Created dashboard with ID %d and org ID %d\n", dash.Id, dash.OrgId)
return dash
} }
func createDummyAcl(dashboardPermission *DashboardPermission, search Search, dashboardId int64) (int64, error) { func createDummyACL(t *testing.T, sqlStore *SQLStore, dashboardPermission *DashboardPermission, search Search, dashboardID int64) int64 {
t.Helper()
acl := &models.DashboardAcl{ acl := &models.DashboardAcl{
OrgID: 1, OrgID: 1,
Created: time.Now(), Created: time.Now(),
Updated: time.Now(), Updated: time.Now(),
Permission: dashboardPermission.Permission, Permission: dashboardPermission.Permission,
DashboardID: dashboardId, DashboardID: dashboardID,
} }
var user *models.User var user *models.User
var err error
if dashboardPermission.User { if dashboardPermission.User {
user, err = createDummyUser() t.Logf("Creating user")
if err != nil { user = createDummyUser(t, sqlStore)
return 0, err
}
acl.UserID = user.Id acl.UserID = user.Id
} }
if dashboardPermission.Team { if dashboardPermission.Team {
team, err := createDummyTeam() t.Logf("Creating team")
if err != nil { team := createDummyTeam(t, sqlStore)
return 0, err
}
if search.UserFromACL { if search.UserFromACL {
user, err = createDummyUser() user = createDummyUser(t, sqlStore)
if err != nil { err := sqlStore.AddTeamMember(user.Id, 1, team.Id, false, 0)
return 0, err require.NoError(t, err)
} t.Logf("Created team member with ID %d", user.Id)
addTeamMemberCmd := &models.AddTeamMemberCommand{
UserId: user.Id,
OrgId: 1,
TeamId: team.Id,
}
err = AddTeamMember(addTeamMemberCmd)
if err != nil {
return 0, err
}
} }
acl.TeamID = team.Id acl.TeamID = team.Id
@@ -306,18 +286,17 @@ func createDummyAcl(dashboardPermission *DashboardPermission, search Search, das
acl.Role = &dashboardPermission.Role acl.Role = &dashboardPermission.Role
} }
updateAclCmd := &models.UpdateDashboardAclCommand{ err := sqlStore.UpdateDashboardACL(dashboardID, []*models.DashboardAcl{acl})
DashboardID: dashboardId, require.NoError(t, err)
Items: []*models.DashboardAcl{acl},
}
err = UpdateDashboardAcl(updateAclCmd)
if user != nil { if user != nil {
return user.Id, err return user.Id
} }
return 0, err return 0
} }
func getDashboards(sqlStore *SQLStore, search Search, aclUserId int64) ([]*dashboardResponse, error) { func getDashboards(t *testing.T, sqlStore *SQLStore, search Search, aclUserID int64) []*dashboardResponse {
t.Helper()
builder := &SQLBuilder{} builder := &SQLBuilder{}
signedInUser := &models.SignedInUser{ signedInUser := &models.SignedInUser{
UserId: 9999999999, UserId: 9999999999,
@@ -335,12 +314,14 @@ func getDashboards(sqlStore *SQLStore, search Search, aclUserId int64) ([]*dashb
signedInUser.OrgRole = models.ROLE_VIEWER signedInUser.OrgRole = models.ROLE_VIEWER
} }
if search.UserFromACL { if search.UserFromACL {
signedInUser.UserId = aclUserId signedInUser.UserId = aclUserID
} }
var res []*dashboardResponse var res []*dashboardResponse
builder.Write("SELECT * FROM dashboard WHERE true") builder.Write("SELECT * FROM dashboard WHERE true")
builder.WriteDashboardPermissionFilter(signedInUser, search.RequiredPermission) builder.WriteDashboardPermissionFilter(signedInUser, search.RequiredPermission)
t.Logf("Searching for dashboards, SQL: %q\n", builder.GetSQLString())
err := sqlStore.engine.SQL(builder.GetSQLString(), builder.params...).Find(&res) err := sqlStore.engine.SQL(builder.GetSQLString(), builder.params...).Find(&res)
return res, err require.NoError(t, err)
return res
} }
-1
View File
@@ -22,7 +22,6 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
_ "github.com/grafana/grafana/pkg/tsdb/mssql"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
_ "github.com/lib/pq" _ "github.com/lib/pq"
+8 -6
View File
@@ -13,8 +13,8 @@ import (
) )
func TestStatsDataAccess(t *testing.T) { func TestStatsDataAccess(t *testing.T) {
InitTestDB(t) sqlStore := InitTestDB(t)
populateDB(t) populateDB(t, sqlStore)
t.Run("Get system stats should not results in error", func(t *testing.T) { t.Run("Get system stats should not results in error", func(t *testing.T) {
query := models.GetSystemStatsQuery{} query := models.GetSystemStatsQuery{}
@@ -57,18 +57,20 @@ func TestStatsDataAccess(t *testing.T) {
}) })
} }
func populateDB(t *testing.T) { func populateDB(t *testing.T, sqlStore *SQLStore) {
t.Helper()
users := make([]models.User, 3) users := make([]models.User, 3)
for i := range users { for i := range users {
cmd := &models.CreateUserCommand{ cmd := models.CreateUserCommand{
Email: fmt.Sprintf("usertest%v@test.com", i), Email: fmt.Sprintf("usertest%v@test.com", i),
Name: fmt.Sprintf("user name %v", i), Name: fmt.Sprintf("user name %v", i),
Login: fmt.Sprintf("user_test_%v_login", i), Login: fmt.Sprintf("user_test_%v_login", i),
OrgName: fmt.Sprintf("Org #%v", i), OrgName: fmt.Sprintf("Org #%v", i),
} }
err := CreateUser(context.Background(), cmd) user, err := sqlStore.CreateUser(context.Background(), cmd)
require.NoError(t, err) require.NoError(t, err)
users[i] = cmd.Result users[i] = *user
} }
// get 1st user's organisation // get 1st user's organisation
+25 -29
View File
@@ -2,6 +2,7 @@ package sqlstore
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@@ -11,14 +12,12 @@ import (
) )
func init() { func init() {
bus.AddHandler("sql", CreateTeam)
bus.AddHandler("sql", UpdateTeam) bus.AddHandler("sql", UpdateTeam)
bus.AddHandler("sql", DeleteTeam) bus.AddHandler("sql", DeleteTeam)
bus.AddHandler("sql", SearchTeams) bus.AddHandler("sql", SearchTeams)
bus.AddHandler("sql", GetTeamById) bus.AddHandler("sql", GetTeamById)
bus.AddHandler("sql", GetTeamsByUser) bus.AddHandler("sql", GetTeamsByUser)
bus.AddHandler("sql", AddTeamMember)
bus.AddHandler("sql", UpdateTeamMember) bus.AddHandler("sql", UpdateTeamMember)
bus.AddHandler("sql", RemoveTeamMember) bus.AddHandler("sql", RemoveTeamMember)
bus.AddHandler("sql", GetTeamMembers) bus.AddHandler("sql", GetTeamMembers)
@@ -43,7 +42,7 @@ func getFilteredUsers(signedInUser *models.SignedInUser, hiddenUsers map[string]
func getTeamMemberCount(filteredUsers []string) string { func getTeamMemberCount(filteredUsers []string) string {
if len(filteredUsers) > 0 { if len(filteredUsers) > 0 {
return `(SELECT COUNT(*) FROM team_member return `(SELECT COUNT(*) FROM team_member
INNER JOIN ` + dialect.Quote("user") + ` ON team_member.user_id = ` + dialect.Quote("user") + `.id INNER JOIN ` + dialect.Quote("user") + ` ON team_member.user_id = ` + dialect.Quote("user") + `.id
WHERE team_member.team_id = team.id AND ` + dialect.Quote("user") + `.login NOT IN (?` + WHERE team_member.team_id = team.id AND ` + dialect.Quote("user") + `.login NOT IN (?` +
strings.Repeat(",?", len(filteredUsers)-1) + ")" + strings.Repeat(",?", len(filteredUsers)-1) + ")" +
@@ -75,28 +74,25 @@ func getTeamSelectSQLBase(filteredUsers []string) string {
` FROM team as team ` ` FROM team as team `
} }
func CreateTeam(cmd *models.CreateTeamCommand) error { func (ss *SQLStore) CreateTeam(name, email string, orgID int64) (models.Team, error) {
return inTransaction(func(sess *DBSession) error { team := models.Team{
if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, 0, sess); err != nil { Name: name,
Email: email,
OrgId: orgID,
Created: time.Now(),
Updated: time.Now(),
}
err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
if isNameTaken, err := isTeamNameTaken(orgID, name, 0, sess); err != nil {
return err return err
} else if isNameTaken { } else if isNameTaken {
return models.ErrTeamNameTaken return models.ErrTeamNameTaken
} }
team := models.Team{
Name: cmd.Name,
Email: cmd.Email,
OrgId: cmd.OrgId,
Created: time.Now(),
Updated: time.Now(),
}
_, err := sess.Insert(&team) _, err := sess.Insert(&team)
cmd.Result = team
return err return err
}) })
return team, err
} }
func UpdateTeam(cmd *models.UpdateTeamCommand) error { func UpdateTeam(cmd *models.UpdateTeamCommand) error {
@@ -152,8 +148,8 @@ func DeleteTeam(cmd *models.DeleteTeamCommand) error {
}) })
} }
func teamExists(orgId int64, teamId int64, sess *DBSession) (bool, error) { func teamExists(orgID int64, teamID int64, sess *DBSession) (bool, error) {
if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", orgId, teamId); err != nil { if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", orgID, teamID); err != nil {
return false, err return false, err
} else if len(res) != 1 { } else if len(res) != 1 {
return false, models.ErrTeamNotFound return false, models.ErrTeamNotFound
@@ -165,7 +161,6 @@ func teamExists(orgId int64, teamId int64, sess *DBSession) (bool, error) {
func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession) (bool, error) { func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession) (bool, error) {
var team models.Team var team models.Team
exists, err := sess.Where("org_id=? and name=?", orgId, name).Get(&team) exists, err := sess.Where("org_id=? and name=?", orgId, name).Get(&team)
if err != nil { if err != nil {
return false, nil return false, nil
} }
@@ -283,26 +278,27 @@ func GetTeamsByUser(query *models.GetTeamsByUserQuery) error {
} }
// AddTeamMember adds a user to a team // AddTeamMember adds a user to a team
func AddTeamMember(cmd *models.AddTeamMemberCommand) error { func (ss *SQLStore) AddTeamMember(userID, orgID, teamID int64, isExternal bool, permission models.PermissionType) error {
return inTransaction(func(sess *DBSession) error { return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
if res, err := sess.Query("SELECT 1 from team_member WHERE org_id=? and team_id=? and user_id=?", cmd.OrgId, cmd.TeamId, cmd.UserId); err != nil { if res, err := sess.Query("SELECT 1 from team_member WHERE org_id=? and team_id=? and user_id=?",
orgID, teamID, userID); err != nil {
return err return err
} else if len(res) == 1 { } else if len(res) == 1 {
return models.ErrTeamMemberAlreadyAdded return models.ErrTeamMemberAlreadyAdded
} }
if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil { if _, err := teamExists(orgID, teamID, sess); err != nil {
return err return err
} }
entity := models.TeamMember{ entity := models.TeamMember{
OrgId: cmd.OrgId, OrgId: orgID,
TeamId: cmd.TeamId, TeamId: teamID,
UserId: cmd.UserId, UserId: userID,
External: cmd.External, External: isExternal,
Created: time.Now(), Created: time.Now(),
Updated: time.Now(), Updated: time.Now(),
Permission: cmd.Permission, Permission: permission,
} }
_, err := sess.Insert(&entity) _, err := sess.Insert(&entity)
+70 -73
View File
@@ -14,32 +14,29 @@ import (
func TestTeamCommandsAndQueries(t *testing.T) { func TestTeamCommandsAndQueries(t *testing.T) {
Convey("Testing Team commands & queries", t, func() { Convey("Testing Team commands & queries", t, func() {
InitTestDB(t) sqlStore := InitTestDB(t)
Convey("Given saved users and two teams", func() { Convey("Given saved users and two teams", func() {
var userIds []int64 var userIds []int64
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
userCmd := &models.CreateUserCommand{ userCmd := models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"), Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i), Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i), Login: fmt.Sprint("loginuser", i),
} }
err := CreateUser(context.Background(), userCmd) user, err := sqlStore.CreateUser(context.Background(), userCmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
userIds = append(userIds, userCmd.Result.Id) userIds = append(userIds, user.Id)
} }
var testOrgId int64 = 1 const testOrgID int64 = 1
group1 := models.CreateTeamCommand{OrgId: testOrgId, Name: "group1 name", Email: "test1@test.com"} team1, err := sqlStore.CreateTeam("group1 name", "test1@test.com", testOrgID)
group2 := models.CreateTeamCommand{OrgId: testOrgId, Name: "group2 name", Email: "test2@test.com"}
err := CreateTeam(&group1)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = CreateTeam(&group2) team2, err := sqlStore.CreateTeam("group2 name", "test2@test.com", testOrgID)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("Should be able to create teams and add users", func() { Convey("Should be able to create teams and add users", func() {
query := &models.SearchTeamsQuery{OrgId: testOrgId, Name: "group1 name", Page: 1, Limit: 10} query := &models.SearchTeamsQuery{OrgId: testOrgID, Name: "group1 name", Page: 1, Limit: 10}
err = SearchTeams(query) err = SearchTeams(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Page, ShouldEqual, 1) So(query.Page, ShouldEqual, 1)
@@ -47,33 +44,33 @@ func TestTeamCommandsAndQueries(t *testing.T) {
team1 := query.Result.Teams[0] team1 := query.Result.Teams[0]
So(team1.Name, ShouldEqual, "group1 name") So(team1.Name, ShouldEqual, "group1 name")
So(team1.Email, ShouldEqual, "test1@test.com") So(team1.Email, ShouldEqual, "test1@test.com")
So(team1.OrgId, ShouldEqual, testOrgId) So(team1.OrgId, ShouldEqual, testOrgID)
So(team1.MemberCount, ShouldEqual, 0) So(team1.MemberCount, ShouldEqual, 0)
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[0]}) err = sqlStore.AddTeamMember(userIds[0], testOrgID, team1.Id, false, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[1], External: true}) err = sqlStore.AddTeamMember(userIds[1], testOrgID, team1.Id, true, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
q1 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id} q1 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id}
err = GetTeamMembers(q1) err = GetTeamMembers(q1)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(q1.Result, ShouldHaveLength, 2) So(q1.Result, ShouldHaveLength, 2)
So(q1.Result[0].TeamId, ShouldEqual, team1.Id) So(q1.Result[0].TeamId, ShouldEqual, team1.Id)
So(q1.Result[0].Login, ShouldEqual, "loginuser0") So(q1.Result[0].Login, ShouldEqual, "loginuser0")
So(q1.Result[0].OrgId, ShouldEqual, testOrgId) So(q1.Result[0].OrgId, ShouldEqual, testOrgID)
So(q1.Result[1].TeamId, ShouldEqual, team1.Id) So(q1.Result[1].TeamId, ShouldEqual, team1.Id)
So(q1.Result[1].Login, ShouldEqual, "loginuser1") So(q1.Result[1].Login, ShouldEqual, "loginuser1")
So(q1.Result[1].OrgId, ShouldEqual, testOrgId) So(q1.Result[1].OrgId, ShouldEqual, testOrgID)
So(q1.Result[1].External, ShouldEqual, true) So(q1.Result[1].External, ShouldEqual, true)
q2 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true} q2 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, External: true}
err = GetTeamMembers(q2) err = GetTeamMembers(q2)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(q2.Result, ShouldHaveLength, 1) So(q2.Result, ShouldHaveLength, 1)
So(q2.Result[0].TeamId, ShouldEqual, team1.Id) So(q2.Result[0].TeamId, ShouldEqual, team1.Id)
So(q2.Result[0].Login, ShouldEqual, "loginuser1") So(q2.Result[0].Login, ShouldEqual, "loginuser1")
So(q2.Result[0].OrgId, ShouldEqual, testOrgId) So(q2.Result[0].OrgId, ShouldEqual, testOrgID)
So(q2.Result[0].External, ShouldEqual, true) So(q2.Result[0].External, ShouldEqual, true)
err = SearchTeams(query) err = SearchTeams(query)
@@ -81,13 +78,13 @@ func TestTeamCommandsAndQueries(t *testing.T) {
team1 = query.Result.Teams[0] team1 = query.Result.Teams[0]
So(team1.MemberCount, ShouldEqual, 2) So(team1.MemberCount, ShouldEqual, 2)
getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgId, Id: team1.Id} getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgID, Id: team1.Id}
err = GetTeamById(getTeamQuery) err = GetTeamById(getTeamQuery)
So(err, ShouldBeNil) So(err, ShouldBeNil)
team1 = getTeamQuery.Result team1 = getTeamQuery.Result
So(team1.Name, ShouldEqual, "group1 name") So(team1.Name, ShouldEqual, "group1 name")
So(team1.Email, ShouldEqual, "test1@test.com") So(team1.Email, ShouldEqual, "test1@test.com")
So(team1.OrgId, ShouldEqual, testOrgId) So(team1.OrgId, ShouldEqual, testOrgID)
So(team1.MemberCount, ShouldEqual, 2) So(team1.MemberCount, ShouldEqual, 2)
}) })
@@ -96,49 +93,48 @@ func TestTeamCommandsAndQueries(t *testing.T) {
err := SetAuthInfo(&models.SetAuthInfoCommand{UserId: userId, AuthModule: "oauth_github", AuthId: "1234567"}) err := SetAuthInfo(&models.SetAuthInfoCommand{UserId: userId, AuthModule: "oauth_github", AuthId: "1234567"})
So(err, ShouldBeNil) So(err, ShouldBeNil)
teamQuery := &models.SearchTeamsQuery{OrgId: testOrgId, Name: "group1 name", Page: 1, Limit: 10} teamQuery := &models.SearchTeamsQuery{OrgId: testOrgID, Name: "group1 name", Page: 1, Limit: 10}
err = SearchTeams(teamQuery) err = SearchTeams(teamQuery)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(teamQuery.Page, ShouldEqual, 1) So(teamQuery.Page, ShouldEqual, 1)
team1 := teamQuery.Result.Teams[0] team1 := teamQuery.Result.Teams[0]
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userId, External: true}) err = sqlStore.AddTeamMember(userId, testOrgID, team1.Id, true, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
memberQuery := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true} memberQuery := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, External: true}
err = GetTeamMembers(memberQuery) err = GetTeamMembers(memberQuery)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(memberQuery.Result, ShouldHaveLength, 1) So(memberQuery.Result, ShouldHaveLength, 1)
So(memberQuery.Result[0].TeamId, ShouldEqual, team1.Id) So(memberQuery.Result[0].TeamId, ShouldEqual, team1.Id)
So(memberQuery.Result[0].Login, ShouldEqual, "loginuser1") So(memberQuery.Result[0].Login, ShouldEqual, "loginuser1")
So(memberQuery.Result[0].OrgId, ShouldEqual, testOrgId) So(memberQuery.Result[0].OrgId, ShouldEqual, testOrgID)
So(memberQuery.Result[0].External, ShouldEqual, true) So(memberQuery.Result[0].External, ShouldEqual, true)
So(memberQuery.Result[0].AuthModule, ShouldEqual, "oauth_github") So(memberQuery.Result[0].AuthModule, ShouldEqual, "oauth_github")
}) })
Convey("Should be able to update users in a team", func() { Convey("Should be able to update users in a team", func() {
userId := userIds[0] userId := userIds[0]
team := group1.Result team := team1
addMemberCmd := models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team.Id, UserId: userId} err = sqlStore.AddTeamMember(userId, testOrgID, team.Id, false, 0)
err = AddTeamMember(&addMemberCmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
err = GetTeamMembers(qBeforeUpdate) err = GetTeamMembers(qBeforeUpdate)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0) So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0)
err = UpdateTeamMember(&models.UpdateTeamMemberCommand{ err = UpdateTeamMember(&models.UpdateTeamMemberCommand{
UserId: userId, UserId: userId,
OrgId: testOrgId, OrgId: testOrgID,
TeamId: team.Id, TeamId: team.Id,
Permission: models.PERMISSION_ADMIN, Permission: models.PERMISSION_ADMIN,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
err = GetTeamMembers(qAfterUpdate) err = GetTeamMembers(qAfterUpdate)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(qAfterUpdate.Result[0].Permission, ShouldEqual, models.PERMISSION_ADMIN) So(qAfterUpdate.Result[0].Permission, ShouldEqual, models.PERMISSION_ADMIN)
@@ -146,12 +142,11 @@ func TestTeamCommandsAndQueries(t *testing.T) {
Convey("Should default to member permission level when updating a user with invalid permission level", func() { Convey("Should default to member permission level when updating a user with invalid permission level", func() {
userID := userIds[0] userID := userIds[0]
team := group1.Result team := team1
addMemberCmd := models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team.Id, UserId: userID} err = sqlStore.AddTeamMember(userID, testOrgID, team.Id, false, 0)
err = AddTeamMember(&addMemberCmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
err = GetTeamMembers(qBeforeUpdate) err = GetTeamMembers(qBeforeUpdate)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0) So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0)
@@ -159,14 +154,14 @@ func TestTeamCommandsAndQueries(t *testing.T) {
invalidPermissionLevel := models.PERMISSION_EDIT invalidPermissionLevel := models.PERMISSION_EDIT
err = UpdateTeamMember(&models.UpdateTeamMemberCommand{ err = UpdateTeamMember(&models.UpdateTeamMemberCommand{
UserId: userID, UserId: userID,
OrgId: testOrgId, OrgId: testOrgID,
TeamId: team.Id, TeamId: team.Id,
Permission: invalidPermissionLevel, Permission: invalidPermissionLevel,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
err = GetTeamMembers(qAfterUpdate) err = GetTeamMembers(qAfterUpdate)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(qAfterUpdate.Result[0].Permission, ShouldEqual, 0) So(qAfterUpdate.Result[0].Permission, ShouldEqual, 0)
@@ -175,8 +170,8 @@ func TestTeamCommandsAndQueries(t *testing.T) {
Convey("Shouldn't be able to update a user not in the team.", func() { Convey("Shouldn't be able to update a user not in the team.", func() {
err = UpdateTeamMember(&models.UpdateTeamMemberCommand{ err = UpdateTeamMember(&models.UpdateTeamMemberCommand{
UserId: 1, UserId: 1,
OrgId: testOrgId, OrgId: testOrgID,
TeamId: group1.Result.Id, TeamId: team1.Id,
Permission: models.PERMISSION_ADMIN, Permission: models.PERMISSION_ADMIN,
}) })
@@ -184,24 +179,24 @@ func TestTeamCommandsAndQueries(t *testing.T) {
}) })
Convey("Should be able to search for teams", func() { Convey("Should be able to search for teams", func() {
query := &models.SearchTeamsQuery{OrgId: testOrgId, Query: "group", Page: 1} query := &models.SearchTeamsQuery{OrgId: testOrgID, Query: "group", Page: 1}
err = SearchTeams(query) err = SearchTeams(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(query.Result.Teams), ShouldEqual, 2) So(len(query.Result.Teams), ShouldEqual, 2)
So(query.Result.TotalCount, ShouldEqual, 2) So(query.Result.TotalCount, ShouldEqual, 2)
query2 := &models.SearchTeamsQuery{OrgId: testOrgId, Query: ""} query2 := &models.SearchTeamsQuery{OrgId: testOrgID, Query: ""}
err = SearchTeams(query2) err = SearchTeams(query2)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(query2.Result.Teams), ShouldEqual, 2) So(len(query2.Result.Teams), ShouldEqual, 2)
}) })
Convey("Should be able to return all teams a user is member of", func() { Convey("Should be able to return all teams a user is member of", func() {
groupId := group2.Result.Id groupId := team2.Id
err := AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]}) err := sqlStore.AddTeamMember(userIds[0], testOrgID, groupId, false, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
query := &models.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]} query := &models.GetTeamsByUserQuery{OrgId: testOrgID, UserId: userIds[0]}
err = GetTeamsByUser(query) err = GetTeamsByUser(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1) So(len(query.Result), ShouldEqual, 1)
@@ -210,63 +205,65 @@ func TestTeamCommandsAndQueries(t *testing.T) {
}) })
Convey("Should be able to remove users from a group", func() { Convey("Should be able to remove users from a group", func() {
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]}) err = sqlStore.AddTeamMember(userIds[0], testOrgID, team1.Id, false, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = RemoveTeamMember(&models.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]}) err = RemoveTeamMember(&models.RemoveTeamMemberCommand{OrgId: testOrgID, TeamId: team1.Id, UserId: userIds[0]})
So(err, ShouldBeNil) So(err, ShouldBeNil)
q2 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: group1.Result.Id} q2 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id}
err = GetTeamMembers(q2) err = GetTeamMembers(q2)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(q2.Result), ShouldEqual, 0) So(len(q2.Result), ShouldEqual, 0)
}) })
Convey("When ProtectLastAdmin is set to true", func() { Convey("When ProtectLastAdmin is set to true", func() {
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], Permission: models.PERMISSION_ADMIN}) err = sqlStore.AddTeamMember(userIds[0], testOrgID, team1.Id, false, models.PERMISSION_ADMIN)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("A user should not be able to remove the last admin", func() { Convey("A user should not be able to remove the last admin", func() {
err = RemoveTeamMember(&models.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], ProtectLastAdmin: true}) err = RemoveTeamMember(&models.RemoveTeamMemberCommand{OrgId: testOrgID, TeamId: team1.Id, UserId: userIds[0], ProtectLastAdmin: true})
So(err, ShouldEqual, models.ErrLastTeamAdmin) So(err, ShouldEqual, models.ErrLastTeamAdmin)
}) })
Convey("A user should be able to remove an admin if there are other admins", func() { Convey("A user should be able to remove an admin if there are other admins", func() {
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[1], Permission: models.PERMISSION_ADMIN}) err = sqlStore.AddTeamMember(userIds[1], testOrgID, team1.Id, false, models.PERMISSION_ADMIN)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = RemoveTeamMember(&models.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], ProtectLastAdmin: true}) err = RemoveTeamMember(&models.RemoveTeamMemberCommand{OrgId: testOrgID, TeamId: team1.Id, UserId: userIds[0], ProtectLastAdmin: true})
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
Convey("A user should not be able to remove the admin permission for the last admin", func() { Convey("A user should not be able to remove the admin permission for the last admin", func() {
err = UpdateTeamMember(&models.UpdateTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], Permission: 0, ProtectLastAdmin: true}) err = UpdateTeamMember(&models.UpdateTeamMemberCommand{OrgId: testOrgID, TeamId: team1.Id, UserId: userIds[0], Permission: 0, ProtectLastAdmin: true})
So(err, ShouldBeError, models.ErrLastTeamAdmin) So(err, ShouldBeError, models.ErrLastTeamAdmin)
}) })
Convey("A user should be able to remove the admin permission if there are other admins", func() { Convey("A user should be able to remove the admin permission if there are other admins", func() {
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[1], Permission: models.PERMISSION_ADMIN}) err = sqlStore.AddTeamMember(userIds[1], testOrgID, team1.Id, false, models.PERMISSION_ADMIN)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = UpdateTeamMember(&models.UpdateTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], Permission: 0, ProtectLastAdmin: true}) err = UpdateTeamMember(&models.UpdateTeamMemberCommand{OrgId: testOrgID, TeamId: team1.Id, UserId: userIds[0], Permission: 0, ProtectLastAdmin: true})
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
}) })
Convey("Should be able to remove a group with users and permissions", func() { Convey("Should be able to remove a group with users and permissions", func() {
groupId := group2.Result.Id groupId := team2.Id
err := AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[1]}) err := sqlStore.AddTeamMember(userIds[1], testOrgID, groupId, false, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]}) err = sqlStore.AddTeamMember(userIds[2], testOrgID, groupId, false, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = testHelperUpdateDashboardAcl(1, models.DashboardAcl{DashboardID: 1, OrgID: testOrgId, Permission: models.PERMISSION_EDIT, TeamID: groupId}) err = testHelperUpdateDashboardAcl(t, sqlStore, 1, models.DashboardAcl{
DashboardID: 1, OrgID: testOrgID, Permission: models.PERMISSION_EDIT, TeamID: groupId,
})
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = DeleteTeam(&models.DeleteTeamCommand{OrgId: testOrgId, Id: groupId}) err = DeleteTeam(&models.DeleteTeamCommand{OrgId: testOrgID, Id: groupId})
So(err, ShouldBeNil) So(err, ShouldBeNil)
query := &models.GetTeamByIdQuery{OrgId: testOrgId, Id: groupId} query := &models.GetTeamByIdQuery{OrgId: testOrgID, Id: groupId}
err = GetTeamById(query) err = GetTeamById(query)
So(err, ShouldEqual, models.ErrTeamNotFound) So(err, ShouldEqual, models.ErrTeamNotFound)
permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: testOrgId} permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: testOrgID}
err = GetDashboardAclInfoList(permQuery) err = GetDashboardAclInfoList(permQuery)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -274,18 +271,18 @@ func TestTeamCommandsAndQueries(t *testing.T) {
}) })
Convey("Should be able to return if user is admin of teams or not", func() { Convey("Should be able to return if user is admin of teams or not", func() {
groupId := group2.Result.Id groupId := team2.Id
err := AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]}) err := sqlStore.AddTeamMember(userIds[0], testOrgID, groupId, false, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[1], Permission: models.PERMISSION_ADMIN}) err = sqlStore.AddTeamMember(userIds[1], testOrgID, groupId, false, models.PERMISSION_ADMIN)
So(err, ShouldBeNil) So(err, ShouldBeNil)
query := &models.IsAdminOfTeamsQuery{SignedInUser: &models.SignedInUser{OrgId: testOrgId, UserId: userIds[0]}} query := &models.IsAdminOfTeamsQuery{SignedInUser: &models.SignedInUser{OrgId: testOrgID, UserId: userIds[0]}}
err = IsAdminOfTeams(query) err = IsAdminOfTeams(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Result, ShouldBeFalse) So(query.Result, ShouldBeFalse)
query = &models.IsAdminOfTeamsQuery{SignedInUser: &models.SignedInUser{OrgId: testOrgId, UserId: userIds[1]}} query = &models.IsAdminOfTeamsQuery{SignedInUser: &models.SignedInUser{OrgId: testOrgID, UserId: userIds[1]}}
err = IsAdminOfTeams(query) err = IsAdminOfTeams(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Result, ShouldBeTrue) So(query.Result, ShouldBeTrue)
@@ -295,29 +292,29 @@ func TestTeamCommandsAndQueries(t *testing.T) {
signedInUser := &models.SignedInUser{Login: "loginuser0"} signedInUser := &models.SignedInUser{Login: "loginuser0"}
hiddenUsers := map[string]struct{}{"loginuser0": {}, "loginuser1": {}} hiddenUsers := map[string]struct{}{"loginuser0": {}, "loginuser1": {}}
teamId := group1.Result.Id teamId := team1.Id
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: teamId, UserId: userIds[0]}) err = sqlStore.AddTeamMember(userIds[0], testOrgID, teamId, false, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: teamId, UserId: userIds[1]}) err = sqlStore.AddTeamMember(userIds[1], testOrgID, teamId, false, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: teamId, UserId: userIds[2]}) err = sqlStore.AddTeamMember(userIds[2], testOrgID, teamId, false, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
searchQuery := &models.SearchTeamsQuery{OrgId: testOrgId, Page: 1, Limit: 10, SignedInUser: signedInUser, HiddenUsers: hiddenUsers} searchQuery := &models.SearchTeamsQuery{OrgId: testOrgID, Page: 1, Limit: 10, SignedInUser: signedInUser, HiddenUsers: hiddenUsers}
err = SearchTeams(searchQuery) err = SearchTeams(searchQuery)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(searchQuery.Result.Teams, ShouldHaveLength, 2) So(searchQuery.Result.Teams, ShouldHaveLength, 2)
team1 := searchQuery.Result.Teams[0] team1 := searchQuery.Result.Teams[0]
So(team1.MemberCount, ShouldEqual, 2) So(team1.MemberCount, ShouldEqual, 2)
searchQueryFilteredByUser := &models.SearchTeamsQuery{OrgId: testOrgId, Page: 1, Limit: 10, UserIdFilter: userIds[0], SignedInUser: signedInUser, HiddenUsers: hiddenUsers} searchQueryFilteredByUser := &models.SearchTeamsQuery{OrgId: testOrgID, Page: 1, Limit: 10, UserIdFilter: userIds[0], SignedInUser: signedInUser, HiddenUsers: hiddenUsers}
err = SearchTeams(searchQueryFilteredByUser) err = SearchTeams(searchQueryFilteredByUser)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(searchQueryFilteredByUser.Result.Teams, ShouldHaveLength, 1) So(searchQueryFilteredByUser.Result.Teams, ShouldHaveLength, 1)
team1 = searchQuery.Result.Teams[0] team1 = searchQuery.Result.Teams[0]
So(team1.MemberCount, ShouldEqual, 2) So(team1.MemberCount, ShouldEqual, 2)
getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgId, Id: teamId, SignedInUser: signedInUser, HiddenUsers: hiddenUsers} getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgID, Id: teamId, SignedInUser: signedInUser, HiddenUsers: hiddenUsers}
err = GetTeamById(getTeamQuery) err = GetTeamById(getTeamQuery)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(getTeamQuery.Result.MemberCount, ShouldEqual, 2) So(getTeamQuery.Result.MemberCount, ShouldEqual, 2)
+17 -15
View File
@@ -31,12 +31,10 @@ func (ss *SQLStore) addUserQueryAndCommandHandlers() {
bus.AddHandler("sql", DisableUser) bus.AddHandler("sql", DisableUser)
bus.AddHandler("sql", BatchDisableUsers) bus.AddHandler("sql", BatchDisableUsers)
bus.AddHandler("sql", DeleteUser) bus.AddHandler("sql", DeleteUser)
bus.AddHandler("sql", UpdateUserPermissions)
bus.AddHandler("sql", SetUserHelpFlag) bus.AddHandler("sql", SetUserHelpFlag)
bus.AddHandlerCtx("sql", CreateUser)
} }
func getOrgIdForNewUser(sess *DBSession, cmd *models.CreateUserCommand) (int64, error) { func getOrgIdForNewUser(sess *DBSession, cmd models.CreateUserCommand) (int64, error) {
if cmd.SkipOrgSetup { if cmd.SkipOrgSetup {
return -1, nil return -1, nil
} }
@@ -191,8 +189,9 @@ func (ss *SQLStore) createUser(ctx context.Context, args userCreationArgs, skipO
return user, nil return user, nil
} }
func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error { func (ss *SQLStore) CreateUser(ctx context.Context, cmd models.CreateUserCommand) (*models.User, error) {
return inTransactionCtx(ctx, func(sess *DBSession) error { var user *models.User
err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error {
orgId, err := getOrgIdForNewUser(sess, cmd) orgId, err := getOrgIdForNewUser(sess, cmd)
if err != nil { if err != nil {
return err return err
@@ -202,13 +201,16 @@ func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
cmd.Email = cmd.Login cmd.Email = cmd.Login
} }
exists, _ := sess.Where("email=? OR login=?", cmd.Email, cmd.Login).Get(&models.User{}) exists, err := sess.Where("email=? OR login=?", cmd.Email, cmd.Login).Get(&models.User{})
if err != nil {
return err
}
if exists { if exists {
return models.ErrUserAlreadyExists return models.ErrUserAlreadyExists
} }
// create user // create user
user := models.User{ user = &models.User{
Email: cmd.Email, Email: cmd.Email,
Name: cmd.Name, Name: cmd.Name,
Login: cmd.Login, Login: cmd.Login,
@@ -243,7 +245,7 @@ func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
sess.UseBool("is_admin") sess.UseBool("is_admin")
if _, err := sess.Insert(&user); err != nil { if _, err := sess.Insert(user); err != nil {
return err return err
} }
@@ -255,8 +257,6 @@ func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
Email: user.Email, Email: user.Email,
}) })
cmd.Result = user
// create org user link // create org user link
if !cmd.SkipOrgSetup { if !cmd.SkipOrgSetup {
orgUser := models.OrgUser{ orgUser := models.OrgUser{
@@ -282,6 +282,8 @@ func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
return nil return nil
}) })
return user, err
} }
func GetUserById(query *models.GetUserByIdQuery) error { func GetUserById(query *models.GetUserByIdQuery) error {
@@ -729,14 +731,14 @@ func deleteUserInTransaction(sess *DBSession, cmd *models.DeleteUserCommand) err
return nil return nil
} }
func UpdateUserPermissions(cmd *models.UpdateUserPermissionsCommand) error { func (ss *SQLStore) UpdateUserPermissions(userID int64, isAdmin bool) error {
return inTransaction(func(sess *DBSession) error { return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
user := models.User{} var user models.User
if _, err := sess.ID(cmd.UserId).Get(&user); err != nil { if _, err := sess.ID(userID).Get(&user); err != nil {
return err return err
} }
user.IsAdmin = cmd.IsGrafanaAdmin user.IsAdmin = isAdmin
sess.UseBool("is_admin") sess.UseBool("is_admin")
_, err := sess.ID(user.Id).Update(&user) _, err := sess.ID(user.Id).Update(&user)
+7 -9
View File
@@ -15,18 +15,16 @@ import (
//nolint:goconst //nolint:goconst
func TestUserAuth(t *testing.T) { func TestUserAuth(t *testing.T) {
InitTestDB(t) sqlStore := InitTestDB(t)
Convey("Given 5 users", t, func() { Convey("Given 5 users", t, func() {
var err error
var cmd *models.CreateUserCommand
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
cmd = &models.CreateUserCommand{ cmd := models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"), Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i), Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i), Login: fmt.Sprint("loginuser", i),
} }
err = CreateUser(context.Background(), cmd) _, err := sqlStore.CreateUser(context.Background(), cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
} }
@@ -46,7 +44,7 @@ func TestUserAuth(t *testing.T) {
login := "loginuser0" login := "loginuser0"
query := &models.GetUserByAuthInfoQuery{Login: login} query := &models.GetUserByAuthInfoQuery{Login: login}
err = GetUserByAuthInfo(query) err := GetUserByAuthInfo(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Result.Login, ShouldEqual, login) So(query.Result.Login, ShouldEqual, login)
@@ -82,7 +80,7 @@ func TestUserAuth(t *testing.T) {
Convey("Can set & locate by AuthModule and AuthId", func() { Convey("Can set & locate by AuthModule and AuthId", func() {
// get nonexistent user_auth entry // get nonexistent user_auth entry
query := &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test"} query := &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test"}
err = GetUserByAuthInfo(query) err := GetUserByAuthInfo(query)
So(err, ShouldEqual, models.ErrUserNotFound) So(err, ShouldEqual, models.ErrUserNotFound)
So(query.Result, ShouldBeNil) So(query.Result, ShouldBeNil)
@@ -144,7 +142,7 @@ func TestUserAuth(t *testing.T) {
// Calling GetUserByAuthInfoQuery on an existing user will populate an entry in the user_auth table // Calling GetUserByAuthInfoQuery on an existing user will populate an entry in the user_auth table
query := &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test", AuthId: "test"} query := &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test", AuthId: "test"}
err = GetUserByAuthInfo(query) err := GetUserByAuthInfo(query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query.Result.Login, ShouldEqual, login) So(query.Result.Login, ShouldEqual, login)
@@ -179,7 +177,7 @@ func TestUserAuth(t *testing.T) {
// Make the first log-in during the past // Make the first log-in during the past
getTime = func() time.Time { return time.Now().AddDate(0, 0, -2) } getTime = func() time.Time { return time.Now().AddDate(0, 0, -2) }
query := &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test1", AuthId: "test1"} query := &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test1", AuthId: "test1"}
err = GetUserByAuthInfo(query) err := GetUserByAuthInfo(query)
getTime = time.Now getTime = time.Now
So(err, ShouldBeNil) So(err, ShouldBeNil)
+34 -37
View File
@@ -20,17 +20,17 @@ func TestUserDataAccess(t *testing.T) {
ss := InitTestDB(t) ss := InitTestDB(t)
Convey("Creates a user", func() { Convey("Creates a user", func() {
cmd := &models.CreateUserCommand{ cmd := models.CreateUserCommand{
Email: "usertest@test.com", Email: "usertest@test.com",
Name: "user name", Name: "user name",
Login: "user_test_login", Login: "user_test_login",
} }
err := CreateUser(context.Background(), cmd) user, err := ss.CreateUser(context.Background(), cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("Loading a user", func() { Convey("Loading a user", func() {
query := models.GetUserByIdQuery{Id: cmd.Result.Id} query := models.GetUserByIdQuery{Id: user.Id}
err := GetUserById(&query) err := GetUserById(&query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -43,18 +43,18 @@ func TestUserDataAccess(t *testing.T) {
}) })
Convey("Creates disabled user", func() { Convey("Creates disabled user", func() {
cmd := &models.CreateUserCommand{ cmd := models.CreateUserCommand{
Email: "usertest@test.com", Email: "usertest@test.com",
Name: "user name", Name: "user name",
Login: "user_test_login", Login: "user_test_login",
IsDisabled: true, IsDisabled: true,
} }
err := CreateUser(context.Background(), cmd) user, err := ss.CreateUser(context.Background(), cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("Loading a user", func() { Convey("Loading a user", func() {
query := models.GetUserByIdQuery{Id: cmd.Result.Id} query := models.GetUserByIdQuery{Id: user.Id}
err := GetUserById(&query) err := GetUserById(&query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -78,18 +78,18 @@ func TestUserDataAccess(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("Creates user assigned to other organization", func() { Convey("Creates user assigned to other organization", func() {
cmd := &models.CreateUserCommand{ cmd := models.CreateUserCommand{
Email: "usertest@test.com", Email: "usertest@test.com",
Name: "user name", Name: "user name",
Login: "user_test_login", Login: "user_test_login",
OrgId: orgCmd.Result.Id, OrgId: orgCmd.Result.Id,
} }
err := CreateUser(context.Background(), cmd) user, err := ss.CreateUser(context.Background(), cmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("Loading a user", func() { Convey("Loading a user", func() {
query := models.GetUserByIdQuery{Id: cmd.Result.Id} query := models.GetUserByIdQuery{Id: user.Id}
err := GetUserById(&query) err := GetUserById(&query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -104,20 +104,20 @@ func TestUserDataAccess(t *testing.T) {
Convey("Don't create user assigned to unknown organization", func() { Convey("Don't create user assigned to unknown organization", func() {
const nonExistingOrgID = 10000 const nonExistingOrgID = 10000
cmd := &models.CreateUserCommand{ cmd := models.CreateUserCommand{
Email: "usertest@test.com", Email: "usertest@test.com",
Name: "user name", Name: "user name",
Login: "user_test_login", Login: "user_test_login",
OrgId: nonExistingOrgID, OrgId: nonExistingOrgID,
} }
err := CreateUser(context.Background(), cmd) _, err := ss.CreateUser(context.Background(), cmd)
So(err, ShouldEqual, models.ErrOrgNotFound) So(err, ShouldEqual, models.ErrOrgNotFound)
}) })
}) })
Convey("Given 5 users", func() { Convey("Given 5 users", func() {
users := createFiveTestUsers(func(i int) *models.CreateUserCommand { users := createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{ return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"), Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i), Name: fmt.Sprint("user", i),
@@ -238,7 +238,7 @@ func TestUserDataAccess(t *testing.T) {
Convey("Can return list users based on their is_disabled flag", func() { Convey("Can return list users based on their is_disabled flag", func() {
ss = InitTestDB(t) ss = InitTestDB(t)
createFiveTestUsers(func(i int) *models.CreateUserCommand { createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{ return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"), Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i), Name: fmt.Sprint("user", i),
@@ -269,7 +269,7 @@ func TestUserDataAccess(t *testing.T) {
So(third, ShouldBeTrue) So(third, ShouldBeTrue)
ss = InitTestDB(t) ss = InitTestDB(t)
users = createFiveTestUsers(func(i int) *models.CreateUserCommand { users = createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{ return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"), Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i), Name: fmt.Sprint("user", i),
@@ -286,7 +286,7 @@ func TestUserDataAccess(t *testing.T) {
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = testHelperUpdateDashboardAcl(1, models.DashboardAcl{ err = testHelperUpdateDashboardAcl(t, ss, 1, models.DashboardAcl{
DashboardID: 1, OrgID: users[0].OrgId, UserID: users[1].Id, DashboardID: 1, OrgID: users[0].OrgId, UserID: users[1].Id,
Permission: models.PERMISSION_EDIT, Permission: models.PERMISSION_EDIT,
}) })
@@ -365,7 +365,7 @@ func TestUserDataAccess(t *testing.T) {
Convey("Should enable all users", func() { Convey("Should enable all users", func() {
ss = InitTestDB(t) ss = InitTestDB(t)
createFiveTestUsers(func(i int) *models.CreateUserCommand { createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{ return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"), Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i), Name: fmt.Sprint("user", i),
@@ -392,7 +392,7 @@ func TestUserDataAccess(t *testing.T) {
Convey("Should disable only specific users", func() { Convey("Should disable only specific users", func() {
ss = InitTestDB(t) ss = InitTestDB(t)
users = createFiveTestUsers(func(i int) *models.CreateUserCommand { users = createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{ return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"), Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i), Name: fmt.Sprint("user", i),
@@ -438,7 +438,7 @@ func TestUserDataAccess(t *testing.T) {
// Since previous tests were destructive // Since previous tests were destructive
ss = InitTestDB(t) ss = InitTestDB(t)
users = createFiveTestUsers(func(i int) *models.CreateUserCommand { users = createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{ return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"), Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i), Name: fmt.Sprint("user", i),
@@ -546,25 +546,22 @@ func TestUserDataAccess(t *testing.T) {
}) })
Convey("Given one grafana admin user", func() { Convey("Given one grafana admin user", func() {
var err error createUserCmd := models.CreateUserCommand{
createUserCmd := &models.CreateUserCommand{
Email: fmt.Sprint("admin", "@test.com"), Email: fmt.Sprint("admin", "@test.com"),
Name: "admin", Name: "admin",
Login: "admin", Login: "admin",
IsAdmin: true, IsAdmin: true,
} }
err = CreateUser(context.Background(), createUserCmd) user, err := ss.CreateUser(context.Background(), createUserCmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("Cannot make themselves a non-admin", func() { Convey("Cannot make themselves a non-admin", func() {
updateUserPermsCmd := models.UpdateUserPermissionsCommand{IsGrafanaAdmin: false, UserId: 1} updatePermsError := ss.UpdateUserPermissions(1, false)
updatePermsError := UpdateUserPermissions(&updateUserPermsCmd)
So(updatePermsError, ShouldEqual, models.ErrLastGrafanaAdmin) So(updatePermsError, ShouldEqual, models.ErrLastGrafanaAdmin)
query := models.GetUserByIdQuery{Id: createUserCmd.Result.Id} query := models.GetUserByIdQuery{Id: user.Id}
getUserError := GetUserById(&query) getUserError := GetUserById(&query)
So(getUserError, ShouldBeNil) So(getUserError, ShouldBeNil)
So(query.Result.IsAdmin, ShouldEqual, true) So(query.Result.IsAdmin, ShouldEqual, true)
@@ -574,33 +571,33 @@ func TestUserDataAccess(t *testing.T) {
Convey("Given one user", func() { Convey("Given one user", func() {
const email = "user@test.com" const email = "user@test.com"
const username = "user" const username = "user"
createUserCmd := &models.CreateUserCommand{ createUserCmd := models.CreateUserCommand{
Email: email, Email: email,
Name: "user", Name: "user",
Login: username, Login: username,
} }
err := CreateUser(context.Background(), createUserCmd) _, err := ss.CreateUser(context.Background(), createUserCmd)
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("When trying to create a new user with the same email, an error is returned", func() { Convey("When trying to create a new user with the same email, an error is returned", func() {
createUserCmd := &models.CreateUserCommand{ createUserCmd := models.CreateUserCommand{
Email: email, Email: email,
Name: "user2", Name: "user2",
Login: "user2", Login: "user2",
SkipOrgSetup: true, SkipOrgSetup: true,
} }
err := CreateUser(context.Background(), createUserCmd) _, err := ss.CreateUser(context.Background(), createUserCmd)
So(err, ShouldEqual, models.ErrUserAlreadyExists) So(err, ShouldEqual, models.ErrUserAlreadyExists)
}) })
Convey("When trying to create a new user with the same login, an error is returned", func() { Convey("When trying to create a new user with the same login, an error is returned", func() {
createUserCmd := &models.CreateUserCommand{ createUserCmd := models.CreateUserCommand{
Email: "user2@test.com", Email: "user2@test.com",
Name: "user2", Name: "user2",
Login: username, Login: username,
SkipOrgSetup: true, SkipOrgSetup: true,
} }
err := CreateUser(context.Background(), createUserCmd) _, err := ss.CreateUser(context.Background(), createUserCmd)
So(err, ShouldEqual, models.ErrUserAlreadyExists) So(err, ShouldEqual, models.ErrUserAlreadyExists)
}) })
}) })
@@ -618,15 +615,15 @@ func GetOrgUsersForTest(query *models.GetOrgUsersQuery) error {
return err return err
} }
func createFiveTestUsers(fn func(i int) *models.CreateUserCommand) []models.User { func createFiveTestUsers(t *testing.T, sqlStore *SQLStore, fn func(i int) *models.CreateUserCommand) []models.User {
var err error t.Helper()
var cmd *models.CreateUserCommand
users := []models.User{} users := []models.User{}
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
cmd = fn(i) cmd := fn(i)
err = CreateUser(context.Background(), cmd) user, err := sqlStore.CreateUser(context.Background(), *cmd)
users = append(users, cmd.Result) users = append(users, *user)
So(err, ShouldBeNil) So(err, ShouldBeNil)
} }
@@ -18,7 +18,6 @@ import (
"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/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"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"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
@@ -27,8 +26,9 @@ import (
// ApplicationInsightsDatasource calls the application insights query API. // ApplicationInsightsDatasource calls the application insights query API.
type ApplicationInsightsDatasource struct { type ApplicationInsightsDatasource struct {
httpClient *http.Client httpClient *http.Client
dsInfo *models.DataSource dsInfo *models.DataSource
pluginManager plugins.Manager
} }
// ApplicationInsightsQuery is the model that holds the information // ApplicationInsightsQuery is the model that holds the information
@@ -210,8 +210,8 @@ func (e *ApplicationInsightsDatasource) executeQuery(ctx context.Context, query
func (e *ApplicationInsightsDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) { func (e *ApplicationInsightsDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
// find plugin // find plugin
plugin, ok := manager.DataSources[dsInfo.Type] plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if !ok { if plugin == nil {
return nil, errors.New("unable to find datasource plugin Azure Application Insights") return nil, errors.New("unable to find datasource plugin Azure Application Insights")
} }
@@ -17,7 +17,6 @@ import (
"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/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"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"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
@@ -26,8 +25,9 @@ import (
// AzureLogAnalyticsDatasource calls the Azure Log Analytics API's // AzureLogAnalyticsDatasource calls the Azure Log Analytics API's
type AzureLogAnalyticsDatasource struct { type AzureLogAnalyticsDatasource struct {
httpClient *http.Client httpClient *http.Client
dsInfo *models.DataSource dsInfo *models.DataSource
pluginManager plugins.Manager
} }
// AzureLogAnalyticsQuery is the query request that is built from the saved values for // AzureLogAnalyticsQuery is the query request that is built from the saved values for
@@ -217,8 +217,8 @@ func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, dsInfo
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion)) req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
// find plugin // find plugin
plugin, ok := manager.DataSources[dsInfo.Type] plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if !ok { if plugin == nil {
return nil, errors.New("unable to find datasource plugin Azure Monitor") return nil, errors.New("unable to find datasource plugin Azure Monitor")
} }
cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor") cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor")
@@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/api/pluginproxy" "github.com/grafana/grafana/pkg/api/pluginproxy"
"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/manager"
"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"
opentracing "github.com/opentracing/opentracing-go" opentracing "github.com/opentracing/opentracing-go"
@@ -26,8 +25,9 @@ import (
// AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported // AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported
type AzureMonitorDatasource struct { type AzureMonitorDatasource struct {
httpClient *http.Client httpClient *http.Client
dsInfo *models.DataSource dsInfo *models.DataSource
pluginManager plugins.Manager
} }
var ( var (
@@ -226,8 +226,8 @@ func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *AzureM
func (e *AzureMonitorDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) { func (e *AzureMonitorDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
// find plugin // find plugin
plugin, ok := manager.DataSources[dsInfo.Type] plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if !ok { if plugin == nil {
return nil, errors.New("unable to find datasource plugin Azure Monitor") return nil, errors.New("unable to find datasource plugin Azure Monitor")
} }
+19 -12
View File
@@ -26,6 +26,7 @@ func init() {
} }
type Service struct { type Service struct {
PluginManager plugins.Manager `inject:""`
} }
func (s *Service) Init() error { func (s *Service) Init() error {
@@ -34,8 +35,9 @@ func (s *Service) Init() error {
// AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services // AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services
type AzureMonitorExecutor struct { type AzureMonitorExecutor struct {
httpClient *http.Client httpClient *http.Client
dsInfo *models.DataSource dsInfo *models.DataSource
pluginManager plugins.Manager
} }
// NewAzureMonitorExecutor initializes a http client // NewAzureMonitorExecutor initializes a http client
@@ -46,8 +48,9 @@ func (s *Service) NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, er
} }
return &AzureMonitorExecutor{ return &AzureMonitorExecutor{
httpClient: httpClient, httpClient: httpClient,
dsInfo: dsInfo, dsInfo: dsInfo,
pluginManager: s.PluginManager,
}, nil }, nil
} }
@@ -82,23 +85,27 @@ func (e *AzureMonitorExecutor) DataQuery(ctx context.Context, dsInfo *models.Dat
} }
azDatasource := &AzureMonitorDatasource{ azDatasource := &AzureMonitorDatasource{
httpClient: e.httpClient, httpClient: e.httpClient,
dsInfo: e.dsInfo, dsInfo: e.dsInfo,
pluginManager: e.pluginManager,
} }
aiDatasource := &ApplicationInsightsDatasource{ aiDatasource := &ApplicationInsightsDatasource{
httpClient: e.httpClient, httpClient: e.httpClient,
dsInfo: e.dsInfo, dsInfo: e.dsInfo,
pluginManager: e.pluginManager,
} }
alaDatasource := &AzureLogAnalyticsDatasource{ alaDatasource := &AzureLogAnalyticsDatasource{
httpClient: e.httpClient, httpClient: e.httpClient,
dsInfo: e.dsInfo, dsInfo: e.dsInfo,
pluginManager: e.pluginManager,
} }
iaDatasource := &InsightsAnalyticsDatasource{ iaDatasource := &InsightsAnalyticsDatasource{
httpClient: e.httpClient, httpClient: e.httpClient,
dsInfo: e.dsInfo, dsInfo: e.dsInfo,
pluginManager: e.pluginManager,
} }
azResult, err := azDatasource.executeTimeSeriesQuery(ctx, azureMonitorQueries, *tsdbQuery.TimeRange) azResult, err := azDatasource.executeTimeSeriesQuery(ctx, azureMonitorQueries, *tsdbQuery.TimeRange)
@@ -15,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/api/pluginproxy" "github.com/grafana/grafana/pkg/api/pluginproxy"
"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/manager"
"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"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
@@ -23,8 +22,9 @@ import (
) )
type InsightsAnalyticsDatasource struct { type InsightsAnalyticsDatasource struct {
httpClient *http.Client httpClient *http.Client
dsInfo *models.DataSource dsInfo *models.DataSource
pluginManager plugins.Manager
} }
type InsightsAnalyticsQuery struct { type InsightsAnalyticsQuery struct {
@@ -187,8 +187,8 @@ func (e *InsightsAnalyticsDatasource) executeQuery(ctx context.Context, query *I
func (e *InsightsAnalyticsDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) { func (e *InsightsAnalyticsDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
// find plugin // find plugin
plugin, ok := manager.DataSources[dsInfo.Type] plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if !ok { if plugin == nil {
return nil, errors.New("unable to find datasource plugin Azure Application Insights") return nil, errors.New("unable to find datasource plugin Azure Application Insights")
} }
+9 -7
View File
@@ -17,7 +17,6 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
@@ -73,6 +72,7 @@ func init() {
} }
type Service struct { type Service struct {
PluginManager plugins.Manager `inject:""`
} }
func (s *Service) Init() error { func (s *Service) Init() error {
@@ -81,8 +81,9 @@ func (s *Service) Init() error {
// Executor executes queries for the CloudMonitoring datasource. // Executor executes queries for the CloudMonitoring datasource.
type Executor struct { type Executor struct {
httpClient *http.Client httpClient *http.Client
dsInfo *models.DataSource dsInfo *models.DataSource
pluginManager plugins.Manager
} }
// NewExecutor returns an Executor. // NewExecutor returns an Executor.
@@ -93,8 +94,9 @@ func (s *Service) NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, er
} }
return &Executor{ return &Executor{
httpClient: httpClient, httpClient: httpClient,
dsInfo: dsInfo, dsInfo: dsInfo,
pluginManager: s.PluginManager,
}, nil }, nil
} }
@@ -534,8 +536,8 @@ func (e *Executor) createRequest(ctx context.Context, dsInfo *models.DataSource,
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion)) req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
// find plugin // find plugin
plugin, ok := manager.DataSources[dsInfo.Type] plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if !ok { if plugin == nil {
return nil, errors.New("unable to find datasource plugin CloudMonitoring") return nil, errors.New("unable to find datasource plugin CloudMonitoring")
} }
+1 -2
View File
@@ -6,7 +6,6 @@ import (
"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/manager"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor" "github.com/grafana/grafana/pkg/tsdb/azuremonitor"
@@ -46,7 +45,7 @@ type Service struct {
PostgresService *postgres.PostgresService `inject:""` PostgresService *postgres.PostgresService `inject:""`
CloudMonitoringService *cloudmonitoring.Service `inject:""` CloudMonitoringService *cloudmonitoring.Service `inject:""`
AzureMonitorService *azuremonitor.Service `inject:""` AzureMonitorService *azuremonitor.Service `inject:""`
PluginManager *manager.PluginManager `inject:""` PluginManager plugins.Manager `inject:""`
registry map[string]func(*models.DataSource) (plugins.DataPlugin, error) registry map[string]func(*models.DataSource) (plugins.DataPlugin, error)
} }