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
pkg/api/acl.go Normal file
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)
}

View File

@@ -266,7 +266,7 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Get("/datasources/id/:name", routing.Wrap(GetDataSourceIdByName), reqSignedIn)
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/health", routing.Wrap(hs.CheckHealth))
apiRoute.Any("/plugins/:pluginId/resources", hs.CallResource)
@@ -288,13 +288,13 @@ func (hs *HTTPServer) registerRoutes() {
// Folders
apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) {
folderRoute.Get("/", routing.Wrap(GetFolders))
folderRoute.Get("/id/:id", routing.Wrap(GetFolderByID))
folderRoute.Get("/", routing.Wrap(hs.GetFolders))
folderRoute.Get("/id/:id", routing.Wrap(hs.GetFolderByID))
folderRoute.Post("/", bind(models.CreateFolderCommand{}), routing.Wrap(hs.CreateFolder))
folderRoute.Group("/:uid", func(folderUidRoute routing.RouteRegister) {
folderUidRoute.Get("/", routing.Wrap(GetFolderByUID))
folderUidRoute.Put("/", bind(models.UpdateFolderCommand{}), routing.Wrap(UpdateFolder))
folderUidRoute.Get("/", routing.Wrap(hs.GetFolderByUID))
folderUidRoute.Put("/", bind(models.UpdateFolderCommand{}), routing.Wrap(hs.UpdateFolder))
folderUidRoute.Delete("/", routing.Wrap(hs.DeleteFolder))
folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) {

View File

@@ -8,7 +8,6 @@ import (
"path/filepath"
"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/dashboards"
@@ -122,7 +121,7 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
meta.FolderUrl = query.Result.GetUrl()
}
svc := dashboards.NewProvisioningService()
svc := dashboards.NewProvisioningService(hs.SQLStore)
provisioningData, err := svc.GetProvisionedDashboardDataByDashboardID(dash.Id)
if err != nil {
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)
if err != nil {
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)
if err != nil {
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,
}
dashSvc := dashboards.NewService()
dashSvc := dashboards.NewService(hs.SQLStore)
dashboard, err := dashSvc.SaveDashboard(dashItem, allowUiUpdate)
if err != nil {
return dashboardSaveErrorToApiResponse(err)
return hs.dashboardSaveErrorToApiResponse(err)
}
if hs.Cfg.EditorsCanAdmin && newDashboard {
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 {
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
if ok := errors.As(err, &dashboardErr); ok {
if body := dashboardErr.Body(); body != nil {
@@ -360,7 +359,7 @@ func dashboardSaveErrorToApiResponse(err error) response.Response {
if ok := errors.As(err, &pluginErr); ok {
message := fmt.Sprintf("The dashboard belongs to plugin %s.", pluginErr.PluginId)
// 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)
}
return response.JSON(412, util.DynMap{"status": "plugin-dashboard", "message": message})

View File

@@ -6,7 +6,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
)
@@ -68,11 +67,9 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext, apiCmd dt
return dashboardGuardianResponse(err)
}
cmd := models.UpdateDashboardAclCommand{}
cmd.DashboardID = dashID
var items []*models.DashboardAcl
for _, item := range apiCmd.Items {
cmd.Items = append(cmd.Items, &models.DashboardAcl{
items = append(items, &models.DashboardAcl{
OrgID: c.OrgId,
DashboardID: dashID,
UserID: item.UserID,
@@ -88,9 +85,9 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext, apiCmd dt
if err != nil {
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 errors.Is(err, guardian.ErrGuardianPermissionExists) || errors.Is(err, guardian.ErrGuardianOverride) {
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)
}
if err := bus.Dispatch(&cmd); err != nil {
if err := updateDashboardACL(hs, dashID, items); err != nil {
if errors.Is(err, models.ErrDashboardAclInfoMissing) ||
errors.Is(err, models.ErrDashboardPermissionDashboardEmpty) {
return response.Error(409, err.Error(), err)

View File

@@ -48,7 +48,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd,
fn: func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 404, sc.resp.Code)
},
}, hs)
@@ -91,7 +91,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd,
fn: func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 403, sc.resp.Code)
},
}, hs)
@@ -151,7 +151,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd,
fn: func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 200, sc.resp.Code)
},
}, hs)
@@ -189,7 +189,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd,
fn: func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code)
},
}, hs)
@@ -217,7 +217,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/dashboards/id/:id/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
callUpdateDashboardPermissions(sc)
callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code)
respJSON, err := jsonMap(sc.resp.Body.Bytes())
require.NoError(t, err)
@@ -260,7 +260,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd,
fn: func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code)
},
}, hs)
@@ -335,13 +335,20 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
cmd: cmd,
fn: func(sc *scenarioContext) {
setUp()
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error {
assert.Len(t, cmd.Items, 4)
return nil
// TODO: Replace this fake with a fake SQLStore instead (once we can use an interface in its stead)
origUpdateDashboardACL := updateDashboardACL
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()
assert.Equal(t, 200, sc.resp.Code)
assert.Len(t, gotItems, 4)
},
}, hs)
})
@@ -353,10 +360,16 @@ func callGetDashboardPermissions(sc *scenarioContext, hs *HTTPServer) {
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
}
func callUpdateDashboardPermissions(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error {
return nil
func callUpdateDashboardPermissions(t *testing.T, sc *scenarioContext) {
t.Helper()
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()
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"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/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards"
@@ -28,7 +29,10 @@ func TestGetHomeDashboard(t *testing.T) {
cfg := setting.NewCfg()
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 {
query.Result = &models.Preferences{
HomeDashboardId: 0,
@@ -102,11 +106,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
return nil
})
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
@@ -165,7 +164,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUp()
CallDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 403, sc.resp.Code)
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) {
state := setUp()
CallDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 403, sc.resp.Code)
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) {
state := setUp()
CallDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 200, sc.resp.Code)
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) {
state := setUp()
CallDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 200, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
})
@@ -281,11 +280,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
})
setting.ViewersCanEdit = false
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
bus.AddHandler("test", func(query *models.GetDashboardsBySlugQuery) error {
dashboards := []*models.Dashboard{fakeDash}
query.Result = dashboards
@@ -354,7 +348,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUp()
CallDeleteDashboardBySlug(sc, hs)
callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 403, sc.resp.Code)
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) {
state := setUp()
CallDeleteDashboardByUID(sc, hs)
callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 403, sc.resp.Code)
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) {
state := setUp()
CallDeleteDashboardBySlug(sc, hs)
callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 403, sc.resp.Code)
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) {
state := setUp()
CallDeleteDashboardByUID(sc, hs)
callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 403, sc.resp.Code)
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) {
state := setUpInner()
CallDeleteDashboardBySlug(sc, hs)
callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 200, sc.resp.Code)
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) {
state := setUpInner()
CallDeleteDashboardByUID(sc, hs)
callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 200, sc.resp.Code)
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) {
state := setUpInner()
CallDeleteDashboardBySlug(sc, hs)
callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 403, sc.resp.Code)
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) {
state := setUpInner()
CallDeleteDashboardByUID(sc, hs)
callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 403, sc.resp.Code)
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) {
state := setUpInner()
CallDeleteDashboardBySlug(sc, hs)
callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 200, sc.resp.Code)
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) {
state := setUpInner()
CallDeleteDashboardByUID(sc, hs)
callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 200, sc.resp.Code)
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) {
state := setUpInner()
CallDeleteDashboardBySlug(sc, hs)
callDeleteDashboardBySlug(sc, hs)
assert.Equal(t, 403, sc.resp.Code)
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) {
state := setUpInner()
CallDeleteDashboardByUID(sc, hs)
callDeleteDashboardByUID(sc, hs)
assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
})
@@ -730,11 +724,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
dashTwo.FolderId = 3
dashTwo.HasAcl = false
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
bus.AddHandler("test", func(query *models.GetDashboardsBySlugQuery) error {
dashboards := []*models.Dashboard{dashOne, dashTwo}
query.Result = dashboards
@@ -743,14 +732,15 @@ func TestDashboardAPIEndpoint(t *testing.T) {
role := models.ROLE_EDITOR
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
CallDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/dash",
"/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 412, sc.resp.Code)
result := sc.ToJSON()
assert.Equal(t, "multiple-slugs-exists", result.Get("status").MustString())
assert.Equal(t, models.ErrDashboardsWithSameSlugExists.Error(), result.Get("message").MustString())
})
assert.Equal(t, 412, sc.resp.Code)
result := sc.ToJSON()
assert.Equal(t, "multiple-slugs-exists", result.Get("status").MustString())
assert.Equal(t, models.ErrDashboardsWithSameSlugExists.Error(), result.Get("message").MustString())
})
})
t.Run("Post dashboard response tests", func(t *testing.T) {
@@ -856,11 +846,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
return nil
})
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
query.Result = nil
return nil
})
bus.AddHandler("test", func(query *models.GetDashboardVersionQuery) error {
query.Result = &models.DashboardVersion{
Data: simplejson.NewFromAny(map[string]interface{}{
@@ -1017,10 +1002,13 @@ func TestDashboardAPIEndpoint(t *testing.T) {
return nil
})
bus.AddHandler("test", func(query *models.GetProvisionedDashboardDataByIdQuery) error {
query.Result = &models.DashboardProvisioning{ExternalId: "/tmp/grafana/dashboards/test/dashboard1.json"}
return nil
origGetProvisionedData := dashboards.GetProvisionedData
t.Cleanup(func() {
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 {
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) {
setUp()
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/dash",
"/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)
result := sc.ToJSON()
assert.Equal(t, models.ErrDashboardCannotDeleteProvisionedDashboard.Error(), result.Get("error").MustString())
})
assert.Equal(t, 400, sc.resp.Code)
result := sc.ToJSON()
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) {
setUp()
CallDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
assert.Equal(t, 400, sc.resp.Code)
result := sc.ToJSON()
@@ -1141,7 +1130,7 @@ func callGetDashboardVersions(sc *scenarioContext) {
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 {
return nil
})
@@ -1150,7 +1139,7 @@ func CallDeleteDashboardBySlug(sc *scenarioContext, hs *HTTPServer) {
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 {
return nil
})
@@ -1187,6 +1176,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
QuotaService: &quota.QuotaService{
Cfg: cfg,
},
PluginManager: &fakePluginManager{},
}
sc := setupScenarioContext(t, url)
@@ -1204,7 +1194,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
dashboards.NewProvisioningService = origProvisioningService
})
dashboards.MockDashboardService(mock)
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService {
dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
return mockDashboardProvisioningService{}
}
@@ -1268,7 +1258,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
dashboards.NewService = origNewDashboardService
dashboards.NewProvisioningService = origProvisioningService
})
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService {
dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
return mockDashboardProvisioningService{}
}
dashboards.MockDashboardService(mock)
@@ -1290,6 +1280,7 @@ type mockDashboardProvisioningService struct {
dashboards.DashboardProvisioningService
}
func (m mockDashboardProvisioningService) GetProvisionedDashboardDataByDashboardID(dashboardId int64) (*models.DashboardProvisioning, error) {
return &models.DashboardProvisioning{}, nil
func (s mockDashboardProvisioningService) GetProvisionedDashboardDataByDashboardID(dashboardID int64) (
*models.DashboardProvisioning, error) {
return nil, nil
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/api/pluginproxy"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/manager"
)
// ProxyDataSourceRequest proxies datasource requests
@@ -35,8 +34,8 @@ func (hs *HTTPServer) ProxyDataSourceRequest(c *models.ReqContext) {
}
// find plugin
plugin, ok := manager.DataSources[ds.Type]
if !ok {
plugin := hs.PluginManager.GetDataSource(ds.Type)
if plugin == nil {
c.JsonApiErr(http.StatusInternalServerError, "Unable to find datasource plugin", err)
return
}

View File

@@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/adapters"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/util"
)
@@ -47,7 +46,7 @@ func (hs *HTTPServer) GetDataSources(c *models.ReqContext) response.Response {
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.TypeName = plugin.Name
} else {
@@ -363,8 +362,8 @@ func (hs *HTTPServer) CallDatasourceResource(c *models.ReqContext) {
}
// find plugin
plugin, ok := manager.DataSources[ds.Type]
if !ok {
plugin := hs.PluginManager.GetDataSource(ds.Type)
if plugin == nil {
c.JsonApiErr(500, "Unable to find datasource plugin", err)
return
}
@@ -428,8 +427,8 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo
return response.Error(500, "Unable to load datasource metadata", err)
}
plugin, ok := hs.PluginManager.GetDatasource(ds.Type)
if !ok {
plugin := hs.PluginManager.GetDataSource(ds.Type)
if plugin == nil {
return response.Error(500, "Unable to find datasource plugin", err)
}

View File

@@ -35,7 +35,11 @@ func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
})
// 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.fakeReq("GET", "/api/datasources").exec()

View File

@@ -3,7 +3,6 @@ package dtos
import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
)
type PluginSetting struct {
@@ -64,6 +63,6 @@ type ImportDashboardCommand struct {
Path string `json:"path"`
Overwrite bool `json:"overwrite"`
Dashboard *simplejson.Json `json:"dashboard"`
Inputs []manager.ImportDashboardInput `json:"inputs"`
Inputs []plugins.ImportDashboardInput `json:"inputs"`
FolderId int64 `json:"folderId"`
}

19
pkg/api/fakes.go Normal file
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
}

View File

@@ -14,8 +14,8 @@ import (
"github.com/grafana/grafana/pkg/util"
)
func GetFolders(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folders, err := s.GetFolders(c.QueryInt64("limit"))
if err != nil {
@@ -35,10 +35,9 @@ func GetFolders(c *models.ReqContext) response.Response {
return response.JSON(200, result)
}
func GetFolderByUID(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil {
return toFolderError(err)
}
@@ -47,8 +46,8 @@ func GetFolderByUID(c *models.ReqContext) response.Response {
return response.JSON(200, toFolderDto(g, folder))
}
func GetFolderByID(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.GetFolderByID(c.ParamsInt64(":id"))
if err != nil {
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 {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
err := s.CreateFolder(&cmd)
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.CreateFolder(cmd.Title, cmd.Uid)
if err != nil {
return toFolderError(err)
}
if hs.Cfg.EditorsCanAdmin {
if err := dashboards.MakeUserAdmin(hs.Bus, c.OrgId, c.SignedInUser.UserId, cmd.Result.Id, true); err != nil {
hs.log.Error("Could not make user admin", "folder", cmd.Result.Title, "user", c.SignedInUser.UserId, "error", err)
if err := s.MakeUserAdmin(c.OrgId, c.SignedInUser.UserId, folder.Id, true); err != nil {
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)
return response.JSON(200, toFolderDto(g, cmd.Result))
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
return response.JSON(200, toFolderDto(g, folder))
}
func UpdateFolder(c *models.ReqContext, cmd models.UpdateFolderCommand) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
func (hs *HTTPServer) UpdateFolder(c *models.ReqContext, cmd models.UpdateFolderCommand) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
err := s.UpdateFolder(c.Params(":uid"), &cmd)
if err != nil {
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
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
if hs.Cfg.IsPanelLibraryEnabled() {
err := hs.LibraryPanelService.DeleteLibraryPanelsInFolder(c, c.Params(":uid"))
if err != nil {

View File

@@ -6,7 +6,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
@@ -14,7 +13,7 @@ import (
)
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"))
if err != nil {
@@ -62,9 +61,8 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.
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"))
if err != nil {
return toFolderError(err)
}
@@ -79,11 +77,9 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.
return toFolderError(models.ErrFolderAccessDenied)
}
cmd := models.UpdateDashboardAclCommand{}
cmd.DashboardID = folder.Id
var items []*models.DashboardAcl
for _, item := range apiCmd.Items {
cmd.Items = append(cmd.Items, &models.DashboardAcl{
items = append(items, &models.DashboardAcl{
OrgID: c.OrgId,
DashboardID: folder.Id,
UserID: item.UserID,
@@ -99,9 +95,9 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.
if err != nil {
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 errors.Is(err, guardian.ErrGuardianPermissionExists) ||
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)
}
if err := bus.Dispatch(&cmd); err != nil {
if err := updateDashboardACL(hs, folder.Id, items); err != nil {
if errors.Is(err, models.ErrDashboardAclInfoMissing) {
err = models.ErrFolderAclInfoMissing
}

View File

@@ -49,7 +49,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
callUpdateFolderPermissions(t, sc)
assert.Equal(t, 404, sc.resp.Code)
},
}, hs)
@@ -92,7 +92,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
callUpdateFolderPermissions(t, sc)
assert.Equal(t, 403, sc.resp.Code)
},
}, hs)
@@ -153,7 +153,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
callUpdateFolderPermissions(t, sc)
assert.Equal(t, 200, sc.resp.Code)
var resp struct {
@@ -205,7 +205,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
callUpdateFolderPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code)
},
}, hs)
@@ -233,7 +233,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
callUpdateFolderPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code)
respJSON, err := jsonMap(sc.resp.Body.Bytes())
require.NoError(t, err)
@@ -279,7 +279,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
callUpdateFolderPermissions(t, sc)
assert.Equal(t, 400, sc.resp.Code)
},
}, hs)
@@ -355,13 +355,19 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error {
assert.Len(t, cmd.Items, 4)
return nil
origUpdateDashboardACL := updateDashboardACL
t.Cleanup(func() {
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()
assert.Equal(t, 200, sc.resp.Code)
assert.Len(t, gotItems, 4)
},
}, hs)
})
@@ -372,10 +378,16 @@ func callGetFolderPermissions(sc *scenarioContext, hs *HTTPServer) {
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
}
func callUpdateFolderPermissions(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error {
return nil
func callUpdateFolderPermissions(t *testing.T, sc *scenarioContext) {
t.Helper()
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()
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"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) {
defer bus.ClearBusHandlers()
hs := HTTPServer{
Cfg: setting.NewCfg(),
}
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
sc.context = c
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
return UpdateFolder(c, cmd)
return hs.UpdateFolder(c, cmd)
})
origNewFolderService := dashboards.NewFolderService
@@ -195,6 +200,8 @@ func updateFolderScenario(t *testing.T, desc string, url string, routePattern st
}
type fakeFolderService struct {
dashboards.FolderService
GetFoldersResult []*models.Folder
GetFoldersError error
GetFolderByUIDResult *models.Folder
@@ -222,9 +229,8 @@ func (s *fakeFolderService) GetFolderByUID(uid string) (*models.Folder, error) {
return s.GetFolderByUIDResult, s.GetFolderByUIDError
}
func (s *fakeFolderService) CreateFolder(cmd *models.CreateFolderCommand) error {
cmd.Result = s.CreateFolderResult
return s.CreateFolderError
func (s *fakeFolderService) CreateFolder(title, uid string) (*models.Folder, error) {
return s.CreateFolderResult, s.CreateFolderError
}
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) {
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
}
}

View File

@@ -43,10 +43,10 @@ func ReadSourceMapFromFS(dir string, path string) ([]byte, error) {
}
type SourceMapStore struct {
sync.Mutex
cache map[string]*sourceMap
cfg *setting.Cfg
readSourceMap ReadSourceMapFn
sync.Mutex
}
func NewSourceMapStore(cfg *setting.Cfg, readSourceMap ReadSourceMapFn) *SourceMapStore {

View File

@@ -5,7 +5,6 @@ import (
"strconv"
"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/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
// the datasource table)
for _, ds := range manager.DataSources {
for _, ds := range hs.PluginManager.DataSources() {
if ds.BuiltIn {
dataSources[ds.Name] = map[string]interface{}{
"type": ds.Type,
"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,
"buildstamp": buildstamp,
"edition": hs.License.Edition(),
"latestVersion": hs.PluginManager.GrafanaLatestVersion,
"hasUpdate": hs.PluginManager.GrafanaHasUpdate,
"latestVersion": hs.PluginManager.GrafanaLatestVersion(),
"hasUpdate": hs.PluginManager.GrafanaHasUpdate(),
"env": setting.Env,
"isEnterprise": hs.License.HasValidLicense(),
},

View File

@@ -39,18 +39,21 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg) (*macaron.Macaron, *HT
})
}
bus.ClearBusHandlers()
bus.AddHandler("sql", sqlstore.GetPluginSettings)
t.Cleanup(bus.ClearBusHandlers)
sqlStore := sqlstore.InitTestDB(t)
pm := &manager.PluginManager{Cfg: cfg, SQLStore: sqlStore}
r := &rendering.RenderingService{Cfg: cfg}
r := &rendering.RenderingService{
Cfg: cfg,
PluginManager: pm,
}
hs := &HTTPServer{
Cfg: cfg,
Bus: bus.GetBus(),
License: &licensing.OSSLicensingService{Cfg: cfg},
RenderService: r,
PluginManager: &manager.PluginManager{Cfg: cfg},
SQLStore: sqlStore,
PluginManager: pm,
}
m := macaron.New()

View File

@@ -13,6 +13,7 @@ import (
"strings"
"sync"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/live"
"github.com/grafana/grafana/pkg/services/search"
@@ -79,7 +80,7 @@ type HTTPServer struct {
License models.Licensing `inject:""`
BackendPluginManager backendplugin.Manager `inject:""`
PluginRequestValidator models.PluginRequestValidator `inject:""`
PluginManager *manager.PluginManager `inject:""`
PluginManager plugins.Manager `inject:""`
SearchService *search.SearchService `inject:""`
ShortURLService *shorturls.ShortURLService `inject:""`
Live *live.GrafanaLive `inject:""`

View File

@@ -400,8 +400,8 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
GoogleTagManagerId: setting.GoogleTagManagerId,
BuildVersion: setting.BuildVersion,
BuildCommit: setting.BuildCommit,
NewGrafanaVersion: hs.PluginManager.GrafanaLatestVersion,
NewGrafanaVersionExists: hs.PluginManager.GrafanaHasUpdate,
NewGrafanaVersion: hs.PluginManager.GrafanaLatestVersion(),
NewGrafanaVersionExists: hs.PluginManager.GrafanaHasUpdate(),
AppName: setting.ApplicationName,
AppNameBodyClass: getAppNameBodyClass(hs.License.HasValidLicense()),
FavIcon: "public/img/fav32.png",

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) {
pc := backend.PluginContext{}
plugin, exists := manager.Plugins[pluginID]
if !exists {
plugin := hs.PluginManager.GetPlugin(pluginID)
if plugin == nil {
return pc, ErrPluginNotFound
}
@@ -74,13 +74,12 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
}
pluginSettingsMap, err := hs.PluginManager.GetPluginSettings(c.OrgId)
if err != nil {
return response.Error(500, "Failed to get list of plugins", err)
}
result := make(dtos.PluginList, 0)
for _, pluginDef := range manager.Plugins {
for _, pluginDef := range hs.PluginManager.Plugins() {
// filter out app sub plugins
if embeddedFilter == "0" && pluginDef.IncludedInAppId != "" {
continue
@@ -130,7 +129,7 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
}
// 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 {
continue
}
@@ -143,11 +142,11 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
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")
def, exists := manager.Plugins[pluginID]
if !exists {
def := hs.PluginManager.GetPlugin(pluginID)
if def == 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,
apiCmd.Dashboard, apiCmd.Overwrite, apiCmd.Inputs, c.SignedInUser, hs.DataService)
if err != nil {
return dashboardSaveErrorToApiResponse(err)
return hs.dashboardSaveErrorToApiResponse(err)
}
return response.JSON(200, dashInfo)
@@ -267,8 +266,8 @@ func (hs *HTTPServer) ImportDashboard(c *models.ReqContext, apiCmd dtos.ImportDa
// /api/plugins/:pluginId/metrics
func (hs *HTTPServer) CollectPluginMetrics(c *models.ReqContext) response.Response {
pluginID := c.Params("pluginId")
plugin, exists := manager.Plugins[pluginID]
if !exists {
plugin := hs.PluginManager.GetPlugin(pluginID)
if plugin == nil {
return response.Error(404, "Plugin not found", nil)
}

View File

@@ -7,19 +7,19 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"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/util"
)
// POST /api/teams
func (hs *HTTPServer) CreateTeam(c *models.ReqContext, cmd models.CreateTeamCommand) response.Response {
cmd.OrgId = c.OrgId
if c.OrgRole == models.ROLE_VIEWER {
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) {
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
// an additional check whether it is an actual user is required
if c.SignedInUser.IsRealUser() {
addMemberCmd := models.AddTeamMemberCommand{
UserId: c.SignedInUser.UserId,
OrgId: cmd.OrgId,
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)
if err := addTeamMember(hs.SQLStore, c.SignedInUser.UserId, c.OrgId, team.Id, false,
models.PERMISSION_ADMIN); err != nil {
c.Logger.Error("Could not add creator to team", "error", err)
}
} 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{
"teamId": cmd.Result.Id,
"teamId": team.Id,
"message": "Team created",
})
}
@@ -175,3 +169,18 @@ func (hs *HTTPServer) UpdateTeamPreferences(c *models.ReqContext, dtoCmd dtos.Up
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)
}

View File

@@ -3,6 +3,7 @@ package api
import (
"testing"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
macaron "gopkg.in/macaron.v1"
@@ -98,20 +99,29 @@ func TestTeamAPIEndpoint(t *testing.T) {
teamName := "team foo"
createTeamCalled := 0
bus.AddHandler("test", func(cmd *models.CreateTeamCommand) error {
createTeamCalled += 1
cmd.Result = models.Team{Name: teamName, Id: 42}
return nil
// TODO: Use a fake SQLStore when it's represented by an interface
origCreateTeam := createTeam
origAddTeamMember := addTeamMember
t.Cleanup(func() {
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
bus.AddHandler("test", func(cmd *models.AddTeamMemberCommand) error {
addTeamMemberCalled += 1
addTeamMember = func(sqlStore *sqlstore.SQLStore, userID, orgID, teamID int64, isExternal bool,
permission models.PermissionType) error {
addTeamMemberCalled++
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) {
stub := &testLogger{}
@@ -128,7 +138,7 @@ func TestTeamAPIEndpoint(t *testing.T) {
assert.Equal(t, createTeamCalled, 1)
assert.Equal(t, addTeamMemberCalled, 0)
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) {

16
pkg/dashboards/ifaces.go Normal file
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
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/login/social"
"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/sqlstore"
@@ -38,6 +39,7 @@ type UsageStatsService struct {
SQLStore *sqlstore.SQLStore `inject:""`
AlertingUsageStats alerting.UsageStatsQuerier `inject:""`
License models.Licensing `inject:""`
PluginManager plugins.Manager `inject:""`
log log.Logger

View File

@@ -58,7 +58,7 @@ func (uss *UsageStatsService) GetUsageReport(ctx context.Context) (UsageReport,
metrics["stats.playlist.count"] = statsQuery.Result.Playlists
metrics["stats.plugins.apps.count"] = len(manager.Apps)
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.active_users.count"] = statsQuery.Result.ActiveUsers
metrics["stats.datasources.count"] = statsQuery.Result.Datasources
@@ -329,8 +329,8 @@ func (uss *UsageStatsService) updateTotalStats() {
}
func (uss *UsageStatsService) shouldBeReported(dsType string) bool {
ds, ok := manager.DataSources[dsType]
if !ok {
ds := uss.PluginManager.GetDataSource(dsType)
if ds == nil {
return false
}

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, 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.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.ActiveUsers, metrics.Get("stats.active_users.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
}
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) {
t.Helper()
originalDataSources := manager.DataSources
t.Cleanup(func() { manager.DataSources = originalDataSources })
manager.DataSources = map[string]*plugins.DataSourcePlugin{
models.DS_ES: {
FrontendPluginBase: plugins.FrontendPluginBase{
PluginBase: plugins.PluginBase{
Signature: "internal",
uss.PluginManager = &fakePluginManager{
dataSources: map[string]*plugins.DataSourcePlugin{
models.DS_ES: {
FrontendPluginBase: plugins.FrontendPluginBase{
PluginBase: plugins.PluginBase{
Signature: "internal",
},
},
},
},
models.DS_PROMETHEUS: {
FrontendPluginBase: plugins.FrontendPluginBase{
PluginBase: plugins.PluginBase{
Signature: "internal",
models.DS_PROMETHEUS: {
FrontendPluginBase: plugins.FrontendPluginBase{
PluginBase: plugins.PluginBase{
Signature: "internal",
},
},
},
},
models.DS_GRAPHITE: {
FrontendPluginBase: plugins.FrontendPluginBase{
PluginBase: plugins.PluginBase{
Signature: "internal",
models.DS_GRAPHITE: {
FrontendPluginBase: plugins.FrontendPluginBase{
PluginBase: plugins.PluginBase{
Signature: "internal",
},
},
},
},
models.DS_MYSQL: {
FrontendPluginBase: plugins.FrontendPluginBase{
PluginBase: plugins.PluginBase{
Signature: "internal",
models.DS_MYSQL: {
FrontendPluginBase: plugins.FrontendPluginBase{
PluginBase: plugins.PluginBase{
Signature: "internal",
},
},
},
},
@@ -595,5 +609,6 @@ func createService(t *testing.T, cfg setting.Cfg) *UsageStatsService {
License: &licensing.OSSLicensingService{},
AlertingUsageStats: &alertingUsageMock{},
externalMetrics: make(map[string]MetricFunc),
PluginManager: &fakePluginManager{},
}
}

View File

@@ -209,18 +209,3 @@ type AlertStateInfoDTO struct {
State AlertStateType `json:"state"`
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
}

View File

@@ -95,15 +95,6 @@ func (dto *DashboardAclInfoDTO) IsDuplicateOf(other *DashboardAclInfoDTO) bool {
return dto.hasSameRoleAs(other) || dto.hasSameUserAs(other) || dto.hasSameTeamAs(other)
}
//
// COMMANDS
//
type UpdateDashboardAclCommand struct {
DashboardID int64
Items []*DashboardAcl
}
//
// QUERIES
//

View File

@@ -367,13 +367,6 @@ type DeleteDashboardCommand struct {
OrgId int64
}
type ValidateDashboardBeforeSaveCommand struct {
OrgId int64
Dashboard *Dashboard
Overwrite bool
Result *ValidateDashboardBeforeSaveResult
}
type DeleteOrphanedProvisionedDashboardsCommand struct {
ReaderNames []string
}
@@ -430,11 +423,6 @@ type GetProvisionedDashboardDataByIdQuery struct {
Result *DashboardProvisioning
}
type GetProvisionedDashboardDataQuery struct {
Name string
Result []*DashboardProvisioning
}
type GetDashboardsBySlugQuery struct {
OrgId int64
Slug string

View File

@@ -32,23 +32,6 @@ type Folder struct {
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
func (cmd *UpdateFolderCommand) UpdateDashboardModel(dashFolder *Dashboard, orgId int64, userId int64) {
dashFolder.OrgId = orgId

View File

@@ -53,10 +53,6 @@ func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() securejsondata.SecureJ
// ---------------------
// QUERIES
type GetPluginSettingsQuery struct {
OrgId int64
Result []*PluginSettingInfoDTO
}
type PluginSettingInfoDTO struct {
OrgId int64

View File

@@ -3,10 +3,55 @@ package plugins
import (
"context"
"github.com/grafana/grafana/pkg/components/simplejson"
"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.
type DataRequestHandler interface {
// HandleRequest handles a data request.
HandleRequest(context.Context, *models.DataSource, DataQuery) (DataResponse, error)
}

View File

@@ -13,13 +13,6 @@ import (
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 {
VariableName string
}
@@ -29,7 +22,7 @@ func (e DashboardInputMissingError) Error() string {
}
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) {
var dashboard *models.Dashboard
if pluginID != "" {
@@ -67,7 +60,7 @@ func (pm *PluginManager) ImportDashboard(pluginID, path string, orgID, folderID
User: user,
}
savedDash, err := dashboards.NewService().ImportDashboard(dto)
savedDash, err := dashboards.NewService(pm.SQLStore).ImportDashboard(dto)
if err != nil {
return plugins.PluginDashboardInfoDTO{}, err
}
@@ -89,12 +82,12 @@ func (pm *PluginManager) ImportDashboard(pluginID, path string, orgID, folderID
type DashTemplateEvaluator struct {
template *simplejson.Json
inputs []ImportDashboardInput
inputs []plugins.ImportDashboardInput
variables map[string]string
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 {
if varType == input.Type && (input.Name == varName || input.Name == "*") {
return &input

View File

@@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
@@ -21,7 +22,7 @@ func TestDashboardImport(t *testing.T) {
dashboards.MockDashboardService(mock)
info, err := pm.ImportDashboard("test-app", "dashboards/connections.json", 1, 0, nil, false,
[]ImportDashboardInput{
[]plugins.ImportDashboardInput{
{Name: "*", Type: "datasource", Value: "graphite"},
}, &models.SignedInUser{UserId: 1, OrgRole: models.ROLE_ADMIN}, nil)
require.NoError(t, err)
@@ -58,7 +59,7 @@ func TestDashboardImport(t *testing.T) {
evaluator := &DashTemplateEvaluator{
template: template,
inputs: []ImportDashboardInput{
inputs: []plugins.ImportDashboardInput{
{Name: "*", Type: "datasource", Value: "my-server"},
},
}

View File

@@ -11,7 +11,7 @@ import (
)
func (pm *PluginManager) GetPluginDashboards(orgId int64, pluginId string) ([]*plugins.PluginDashboardInfoDTO, error) {
plugin, exists := Plugins[pluginId]
plugin, exists := pm.plugins[pluginId]
if !exists {
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) {
plugin, exists := Plugins[pluginId]
plugin, exists := pm.plugins[pluginId]
if !exists {
return nil, plugins.PluginNotFoundError{PluginID: pluginId}
}

View File

@@ -21,19 +21,17 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil"
)
var (
DataSources map[string]*plugins.DataSourcePlugin
Panels map[string]*plugins.PanelPlugin
StaticRoutes []*plugins.PluginStaticRoute
Apps map[string]*plugins.AppPlugin
Plugins map[string]*plugins.PluginBase
PluginTypes map[string]interface{}
Renderer *plugins.RendererPlugin
plog log.Logger
)
@@ -54,30 +52,36 @@ type PluginScanner struct {
type PluginManager struct {
BackendPluginManager backendplugin.Manager `inject:""`
Cfg *setting.Cfg `inject:""`
SQLStore *sqlstore.SQLStore `inject:""`
log log.Logger
scanningErrors []error
// 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.
AllowUnsignedPluginsCondition unsignedPluginConditionFunc
GrafanaLatestVersion string
GrafanaHasUpdate bool
grafanaLatestVersion string
grafanaHasUpdate bool
pluginScanningErrors map[string]plugins.PluginError
renderer *plugins.RendererPlugin
dataSources map[string]*plugins.DataSourcePlugin
plugins map[string]*plugins.PluginBase
}
func init() {
registry.RegisterService(&PluginManager{})
registry.RegisterService(&PluginManager{
dataSources: map[string]*plugins.DataSourcePlugin{},
})
}
func (pm *PluginManager) Init() error {
pm.log = log.New("plugins")
plog = log.New("plugins")
DataSources = map[string]*plugins.DataSourcePlugin{}
StaticRoutes = []*plugins.PluginStaticRoute{}
Panels = map[string]*plugins.PanelPlugin{}
Apps = map[string]*plugins.AppPlugin{}
Plugins = map[string]*plugins.PluginBase{}
pm.plugins = map[string]*plugins.PluginBase{}
PluginTypes = map[string]interface{}{
"panel": plugins.PanelPlugin{},
"datasource": plugins.DataSourcePlugin{},
@@ -134,22 +138,22 @@ func (pm *PluginManager) Init() error {
StaticRoutes = append(StaticRoutes, staticRoutes...)
}
for _, ds := range DataSources {
for _, ds := range pm.dataSources {
staticRoutes := ds.InitFrontendPlugin(pm.Cfg)
StaticRoutes = append(StaticRoutes, staticRoutes...)
}
for _, app := range Apps {
staticRoutes := app.InitApp(Panels, DataSources, pm.Cfg)
staticRoutes := app.InitApp(Panels, pm.dataSources, pm.Cfg)
StaticRoutes = append(StaticRoutes, staticRoutes...)
}
if Renderer != nil {
staticRoutes := Renderer.InitFrontendPlugin(pm.Cfg)
if pm.renderer != nil {
staticRoutes := pm.renderer.InitFrontendPlugin(pm.Cfg)
StaticRoutes = append(StaticRoutes, staticRoutes...)
}
for _, p := range Plugins {
for _, p := range pm.plugins {
if p.IsCorePlugin {
p.Signature = plugins.PluginSignatureInternal
} else {
@@ -178,6 +182,48 @@ func (pm *PluginManager) Run(ctx context.Context) error {
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.
func (pm *PluginManager) scanPluginPaths() error {
for pluginID, settings := range pm.Cfg.PluginSettings {
@@ -315,13 +361,13 @@ func (pm *PluginManager) loadPlugin(jsonParser *json.Decoder, pluginBase *plugin
var pb *plugins.PluginBase
switch p := plug.(type) {
case *plugins.DataSourcePlugin:
DataSources[p.Id] = p
pm.dataSources[p.Id] = p
pb = &p.PluginBase
case *plugins.PanelPlugin:
Panels[p.Id] = p
pb = &p.PluginBase
case *plugins.RendererPlugin:
Renderer = p
pm.renderer = p
pb = &p.PluginBase
case *plugins.AppPlugin:
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))
}
if p, exists := Plugins[pb.Id]; exists {
if p, exists := pm.plugins[pb.Id]; exists {
pm.log.Warn("Plugin is duplicate", "id", pb.Id)
scanner.errors = append(scanner.errors, plugins.DuplicatePluginError{Plugin: pb, ExistingPlugin: p})
return nil
@@ -360,20 +406,11 @@ func (pm *PluginManager) loadPlugin(jsonParser *json.Decoder, pluginBase *plugin
pb.SignatureType = pluginBase.SignatureType
pb.SignatureOrg = pluginBase.SignatureOrg
Plugins[pb.Id] = pb
pm.plugins[pb.Id] = pb
pm.log.Debug("Successfully added plugin", "id", pb.Id)
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 {
// 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
@@ -545,7 +582,7 @@ func (pm *PluginManager) ScanningErrors() []plugins.PluginError {
}
func (pm *PluginManager) GetPluginMarkdown(pluginId string, name string) ([]byte, error) {
plug, exists := Plugins[pluginId]
plug, exists := pm.plugins[pluginId]
if !exists {
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.
func (pm *PluginManager) GetDataPlugin(pluginID string) plugins.DataPlugin {
if p, exists := DataSources[pluginID]; exists && p.CanHandleDataQueries() {
func (pm *PluginManager) GetDataPlugin(id string) plugins.DataPlugin {
if p, exists := pm.dataSources[id]; exists && p.CanHandleDataQueries() {
return p
}
// XXX: Might other plugins implement DataPlugin?
p := pm.BackendPluginManager.GetDataPlugin(pluginID)
p := pm.BackendPluginManager.GetDataPlugin(id)
if p != nil {
return p.(plugins.DataPlugin)
}

View File

@@ -30,9 +30,9 @@ func TestPluginManager_Init(t *testing.T) {
require.NoError(t, err)
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.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.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)
@@ -114,15 +114,15 @@ func TestPluginManager_Init(t *testing.T) {
require.Empty(t, pm.scanningErrors)
const pluginID = "test"
assert.NotNil(t, Plugins[pluginID])
assert.Equal(t, "datasource", Plugins[pluginID].Type)
assert.Equal(t, "Test", Plugins[pluginID].Name)
assert.Equal(t, pluginID, Plugins[pluginID].Id)
assert.Equal(t, "1.0.0", Plugins[pluginID].Info.Version)
assert.Equal(t, plugins.PluginSignatureValid, Plugins[pluginID].Signature)
assert.Equal(t, plugins.GrafanaType, Plugins[pluginID].SignatureType)
assert.Equal(t, "Grafana Labs", Plugins[pluginID].SignatureOrg)
assert.False(t, Plugins[pluginID].IsCorePlugin)
assert.NotNil(t, pm.plugins[pluginID])
assert.Equal(t, "datasource", pm.plugins[pluginID].Type)
assert.Equal(t, "Test", pm.plugins[pluginID].Name)
assert.Equal(t, pluginID, pm.plugins[pluginID].Id)
assert.Equal(t, "1.0.0", pm.plugins[pluginID].Info.Version)
assert.Equal(t, plugins.PluginSignatureValid, pm.plugins[pluginID].Signature)
assert.Equal(t, plugins.GrafanaType, pm.plugins[pluginID].SignatureType)
assert.Equal(t, "Grafana Labs", pm.plugins[pluginID].SignatureOrg)
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) {
@@ -139,7 +139,7 @@ func TestPluginManager_Init(t *testing.T) {
require.NoError(t, err)
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) {
@@ -157,15 +157,15 @@ func TestPluginManager_Init(t *testing.T) {
require.Empty(t, pm.scanningErrors)
const pluginID = "test"
assert.NotNil(t, Plugins[pluginID])
assert.Equal(t, "datasource", Plugins[pluginID].Type)
assert.Equal(t, "Test", Plugins[pluginID].Name)
assert.Equal(t, pluginID, Plugins[pluginID].Id)
assert.Equal(t, "1.0.0", Plugins[pluginID].Info.Version)
assert.Equal(t, plugins.PluginSignatureValid, Plugins[pluginID].Signature)
assert.Equal(t, plugins.PrivateType, Plugins[pluginID].SignatureType)
assert.Equal(t, "Will Browne", Plugins[pluginID].SignatureOrg)
assert.False(t, Plugins[pluginID].IsCorePlugin)
assert.NotNil(t, pm.plugins[pluginID])
assert.Equal(t, "datasource", pm.plugins[pluginID].Type)
assert.Equal(t, "Test", pm.plugins[pluginID].Name)
assert.Equal(t, pluginID, pm.plugins[pluginID].Id)
assert.Equal(t, "1.0.0", pm.plugins[pluginID].Info.Version)
assert.Equal(t, plugins.PluginSignatureValid, pm.plugins[pluginID].Signature)
assert.Equal(t, plugins.PrivateType, pm.plugins[pluginID].SignatureType)
assert.Equal(t, "Will Browne", pm.plugins[pluginID].SignatureOrg)
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) {
@@ -181,7 +181,7 @@ func TestPluginManager_Init(t *testing.T) {
err := pm.Init()
require.NoError(t, err)
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) {
@@ -197,7 +197,7 @@ func TestPluginManager_Init(t *testing.T) {
err := pm.Init()
require.NoError(t, err)
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,
},
BackendPluginManager: &fakeBackendPluginManager{},
dataSources: map[string]*plugins.DataSourcePlugin{},
}
for _, cb := range cbs {
cb(pm)

View File

@@ -1,23 +1,22 @@
package manager
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
func (pm *PluginManager) GetPluginSettings(orgId int64) (map[string]*models.PluginSettingInfoDTO, error) {
query := models.GetPluginSettingsQuery{OrgId: orgId}
if err := bus.Dispatch(&query); err != nil {
func (pm *PluginManager) GetPluginSettings(orgID int64) (map[string]*models.PluginSettingInfoDTO, error) {
pluginSettings, err := pm.SQLStore.GetPluginSettings(orgID)
if err != nil {
return nil, err
}
pluginMap := make(map[string]*models.PluginSettingInfoDTO)
for _, plug := range query.Result {
for _, plug := range pluginSettings {
pluginMap[plug.PluginId] = plug
}
for _, pluginDef := range Plugins {
for _, pluginDef := range pm.plugins {
// ignore entries that exists
if _, ok := pluginMap[pluginDef.Id]; ok {
continue
@@ -26,7 +25,7 @@ func (pm *PluginManager) GetPluginSettings(orgId int64) (map[string]*models.Plug
// default to enabled true
opt := &models.PluginSettingInfoDTO{
PluginId: pluginDef.Id,
OrgId: orgId,
OrgId: orgID,
Enabled: true,
}
@@ -72,7 +71,7 @@ func (pm *PluginManager) GetEnabledPlugins(orgID int64) (*plugins.EnabledPlugins
}
// 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 {
enabledPlugins.DataSources[dsID] = ds
}

View File

@@ -16,19 +16,19 @@ var (
httpClient = http.Client{Timeout: 10 * time.Second}
)
type GrafanaNetPlugin struct {
type grafanaNetPlugin struct {
Slug string `json:"slug"`
Version string `json:"version"`
}
type GithubLatest struct {
type gitHubLatest struct {
Stable string `json:"stable"`
Testing string `json:"testing"`
}
func getAllExternalPluginSlugs() string {
func (pm *PluginManager) getAllExternalPluginSlugs() string {
var result []string
for _, plug := range Plugins {
for _, plug := range pm.plugins {
if plug.IsCorePlugin {
continue
}
@@ -46,7 +46,7 @@ func (pm *PluginManager) checkForUpdates() {
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)
if err != nil {
log.Tracef("Failed to get plugins repo from grafana.com, %v", err.Error())
@@ -64,14 +64,14 @@ func (pm *PluginManager) checkForUpdates() {
return
}
gNetPlugins := []GrafanaNetPlugin{}
gNetPlugins := []grafanaNetPlugin{}
err = json.Unmarshal(body, &gNetPlugins)
if err != nil {
log.Tracef("Failed to unmarshal plugin repo, reading response from grafana.com, %v", err.Error())
return
}
for _, plug := range Plugins {
for _, plug := range pm.plugins {
for _, gplug := range gNetPlugins {
if gplug.Slug == plug.Id {
plug.GrafanaNetVersion = gplug.Version
@@ -104,24 +104,24 @@ func (pm *PluginManager) checkForUpdates() {
return
}
var githubLatest GithubLatest
err = json.Unmarshal(body, &githubLatest)
var latest gitHubLatest
err = json.Unmarshal(body, &latest)
if err != nil {
log.Tracef("Failed to unmarshal github.com latest, reading response from github.com: %v", err.Error())
return
}
if strings.Contains(setting.BuildVersion, "-") {
pm.GrafanaLatestVersion = githubLatest.Testing
pm.GrafanaHasUpdate = !strings.HasPrefix(setting.BuildVersion, githubLatest.Testing)
pm.grafanaLatestVersion = latest.Testing
pm.grafanaHasUpdate = !strings.HasPrefix(setting.BuildVersion, latest.Testing)
} else {
pm.GrafanaLatestVersion = githubLatest.Stable
pm.GrafanaHasUpdate = githubLatest.Stable != setting.BuildVersion
pm.grafanaLatestVersion = latest.Stable
pm.grafanaHasUpdate = latest.Stable != 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 {
pm.GrafanaHasUpdate = currVersion.LessThan(latestVersion)
pm.grafanaHasUpdate = currVersion.LessThan(latestVersion)
}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/tsdb"
)
@@ -22,6 +23,7 @@ func init() {
type Service struct {
DataService *tsdb.Service `inject:""`
PluginManager *manager.PluginManager `inject:""`
SQLStore *sqlstore.SQLStore `inject:""`
logger log.Logger
}
@@ -40,19 +42,19 @@ func (s *Service) Run(ctx context.Context) error {
func (s *Service) updateAppDashboards() {
s.logger.Debug("Looking for app dashboard updates")
query := models.GetPluginSettingsQuery{OrgId: 0}
if err := bus.Dispatch(&query); err != nil {
pluginSettings, err := s.SQLStore.GetPluginSettings(0)
if err != nil {
s.logger.Error("Failed to get all plugin settings", "error", err)
return
}
for _, pluginSetting := range query.Result {
for _, pluginSetting := range pluginSettings {
// ignore disabled plugins
if !pluginSetting.Enabled {
continue
}
if pluginDef, exists := manager.Plugins[pluginSetting.PluginId]; exists {
if pluginDef := s.PluginManager.GetPlugin(pluginSetting.PluginId); pluginDef != nil {
if pluginDef.Info.Version != pluginSetting.PluginVersion {
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)
if event.Enabled {
s.syncPluginDashboards(manager.Plugins[event.PluginId], event.OrgId)
s.syncPluginDashboards(s.PluginManager.GetPlugin(event.PluginId), event.OrgId)
} else {
query := models.GetDashboardsByPluginIdQuery{PluginId: event.PluginId, OrgId: event.OrgId}
if err := bus.Dispatch(&query); err != nil {

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)
}

View File

@@ -10,10 +10,10 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
tlog "github.com/opentracing/opentracing-go/log"
@@ -27,7 +27,7 @@ type AlertEngine struct {
RenderService rendering.Service `inject:""`
Bus bus.Bus `inject:""`
RequestValidator models.PluginRequestValidator `inject:""`
DataService *tsdb.Service `inject:""`
DataService plugins.DataRequestHandler `inject:""`
execQueue chan *Job
ticker *Ticker

View File

@@ -61,7 +61,7 @@ func findPanelQueryByRefID(panel *simplejson.Json, refID string) *simplejson.Jso
func copyJSON(in json.Marshaler) (*simplejson.Json, error) {
rawJSON, err := in.MarshalJSON()
if err != nil {
return nil, err
return nil, fmt.Errorf("JSON marshaling failed: %w", err)
}
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
// in the first validation pass.
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
}

View File

@@ -3,11 +3,10 @@ package dashboards
import (
"time"
"github.com/grafana/grafana/pkg/bus"
"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
rtViewer := models.ROLE_VIEWER
@@ -43,12 +42,7 @@ func MakeUserAdmin(bus bus.Bus, orgID int64, userID int64, dashboardID int64, se
)
}
aclCmd := &models.UpdateDashboardAclCommand{
DashboardID: dashboardID,
Items: items,
}
if err := bus.Dispatch(aclCmd); err != nil {
if err := dr.dashboardStore.UpdateDashboardACL(dashboardID, items); err != nil {
return err
}

View File

@@ -1,10 +1,13 @@
package dashboards
import (
"fmt"
"strings"
"time"
"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/bus"
@@ -20,6 +23,7 @@ type DashboardService interface {
SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
ImportDashboard(dto *SaveDashboardDTO) (*models.Dashboard, 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.
@@ -27,21 +31,22 @@ type DashboardProvisioningService interface {
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardID(DashboardId int64) (*models.DashboardProvisioning, error)
UnprovisionDashboard(dashboardId int64) error
DeleteProvisionedDashboard(dashboardId int64, orgId int64) error
GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
UnprovisionDashboard(dashboardID int64) error
DeleteProvisionedDashboard(dashboardID int64, orgID int64) error
}
// NewService is a factory for creating a new dashboard service.
var NewService = func() DashboardService {
var NewService = func(store dashboards.Store) DashboardService {
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.
var NewProvisioningService = func() DashboardProvisioningService {
return NewService().(*dashboardServiceImpl)
var NewProvisioningService = func(store dashboards.Store) DashboardProvisioningService {
return NewService(store).(*dashboardServiceImpl)
}
type SaveDashboardDTO struct {
@@ -54,35 +59,32 @@ type SaveDashboardDTO struct {
}
type dashboardServiceImpl struct {
orgId int64
user *models.SignedInUser
log log.Logger
dashboardStore dashboards.Store
orgId int64
user *models.SignedInUser
log log.Logger
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
cmd := &models.GetProvisionedDashboardDataQuery{Name: name}
err := bus.Dispatch(cmd)
if err != nil {
return nil, err
}
return cmd.Result, nil
return dr.dashboardStore.GetProvisionedDashboardData(name)
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(dashboardId int64) (*models.DashboardProvisioning, error) {
cmd := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: dashboardId}
err := bus.Dispatch(cmd)
if err != nil {
return nil, err
}
return cmd.Result, nil
// GetProvisionedData gets provisioned dashboard data.
//
// Stubbable by tests.
var GetProvisionedData = func(store dashboards.Store, dashboardID int64) (*models.DashboardProvisioning, error) {
return store.GetProvisionedDataByDashboardID(dashboardID)
}
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) {
dash := dto.Dashboard
dash.OrgId = dto.OrgId
dash.Title = strings.TrimSpace(dash.Title)
dash.Data.Set("title", dash.Title)
dash.SetUid(strings.TrimSpace(dash.Uid))
@@ -109,29 +111,18 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
return nil, err
}
if validateAlerts {
validateAlertsCmd := models.ValidateDashboardAlertsCommand{
OrgId: dto.OrgId,
Dashboard: dash,
User: dto.User,
}
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
if shouldValidateAlerts {
if err := validateAlerts(dash, dto.User); err != nil {
return nil, err
}
}
validateBeforeSaveCmd := models.ValidateDashboardBeforeSaveCommand{
OrgId: dto.OrgId,
Dashboard: dash,
Overwrite: dto.Overwrite,
}
if err := bus.Dispatch(&validateBeforeSaveCmd); err != nil {
isParentFolderChanged, err := dr.dashboardStore.ValidateDashboardBeforeSave(dash, dto.Overwrite)
if err != nil {
return nil, err
}
if validateBeforeSaveCmd.Result.IsParentFolderChanged {
if isParentFolderChanged {
folderGuardian := guardian.New(dash.FolderId, dto.OrgId, dto.User)
if canSave, err := folderGuardian.CanSave(); err != nil || !canSave {
if err != nil {
@@ -178,6 +169,11 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
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 {
if setting.MinRefreshInterval == "" {
return nil
@@ -191,11 +187,11 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error {
minRefreshInterval, err := gtime.ParseDuration(setting.MinRefreshInterval)
if err != nil {
return err
return fmt.Errorf("parsing min refresh interval %q failed: %w", setting.MinRefreshInterval, err)
}
d, err := gtime.ParseDuration(refresh)
if err != nil {
return err
return fmt.Errorf("parsing refresh duration %q failed: %w", refresh, err)
}
if d < minRefreshInterval {
@@ -205,14 +201,17 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error {
return nil
}
func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error {
alertCmd := models.UpdateDashboardAlertsCommand{
OrgId: dto.OrgId,
Dashboard: cmd.Result,
User: dto.User,
// UpdateAlerting updates alerting.
//
// Stubbable by tests.
var UpdateAlerting = func(store dashboards.Store, orgID int64, dashboard *models.Dashboard, user *models.SignedInUser) error {
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,
@@ -234,24 +233,18 @@ func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO,
return nil, err
}
saveCmd := &models.SaveProvisionedDashboardCommand{
DashboardCmd: cmd,
DashboardProvisioning: provisioning,
}
// dashboard
err = bus.Dispatch(saveCmd)
dash, err := dr.dashboardStore.SaveProvisionedDashboard(*cmd, provisioning)
if err != nil {
return nil, err
}
// alerts
err = dr.updateAlerting(cmd, dto)
if err != nil {
if err := UpdateAlerting(dr.dashboardStore, dto.OrgId, dash, dto.User); err != nil {
return nil, err
}
return cmd.Result, nil
return dash, nil
}
func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDashboardDTO) (*models.Dashboard, error) {
@@ -264,22 +257,23 @@ func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDash
return nil, err
}
err = bus.Dispatch(cmd)
dash, err := dr.dashboardStore.SaveDashboard(*cmd)
if err != nil {
return nil, err
}
err = dr.updateAlerting(cmd, dto)
if err != nil {
if err := UpdateAlerting(dr.dashboardStore, dto.OrgId, dash, dto.User); err != nil {
return nil, err
}
return cmd.Result, nil
return dash, nil
}
func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) {
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)
}
@@ -288,17 +282,16 @@ func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO, allowUiUpda
return nil, err
}
err = bus.Dispatch(cmd)
dash, err := dr.dashboardStore.SaveDashboard(*cmd)
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
}
err = dr.updateAlerting(cmd, dto)
if err != nil {
return nil, err
}
return cmd.Result, nil
return dash, nil
}
// 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)
}
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 {
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)
}
@@ -359,6 +355,7 @@ type FakeDashboardService struct {
SaveDashboardResult *models.Dashboard
SaveDashboardError error
SavedDashboards []*SaveDashboardDTO
ProvisionedDashData *models.DashboardProvisioning
}
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
}
func (s *FakeDashboardService) GetProvisionedDashboardDataByDashboardID(id int64) (*models.DashboardProvisioning, error) {
return s.ProvisionedDashData, nil
}
func MockDashboardService(mock *FakeDashboardService) {
NewService = func() DashboardService {
NewService = func(dashboards.Store) DashboardService {
return mock
}
}

View File

@@ -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,
}
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"testing"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
@@ -17,8 +18,10 @@ func TestDashboardService(t *testing.T) {
Convey("Dashboard service tests", t, func() {
bus.ClearBusHandlers()
fakeStore := fakeDashboardStore{}
service := &dashboardServiceImpl{
log: log.New("test.logger"),
log: log.New("test.logger"),
dashboardStore: &fakeStore,
}
origNewDashboardGuardian := guardian.New
@@ -51,19 +54,13 @@ func TestDashboardService(t *testing.T) {
})
Convey("When saving a dashboard should validate uid", func() {
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
return nil
})
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})
}
testCases := []struct {
Uid string
@@ -89,27 +86,23 @@ func TestDashboardService(t *testing.T) {
})
Convey("Should return validation error if dashboard is provisioned", func() {
provisioningValidated := false
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
cmd.Result = &models.DashboardProvisioning{}
return nil
t.Cleanup(func() {
fakeStore.provisionedData = nil
})
fakeStore.provisionedData = &models.DashboardProvisioning{}
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
return nil
})
}
dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1}
_, err := service.SaveDashboard(dto, false)
So(provisioningValidated, ShouldBeTrue)
So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard)
})
@@ -121,21 +114,20 @@ func TestDashboardService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
return nil
})
}
dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1}
_, err := service.SaveDashboard(dto, true)
So(provisioningValidated, ShouldBeFalse)
So(err, ShouldNotBeNil)
So(err, ShouldBeNil)
})
Convey("Should return validation error if alert data is invalid", func() {
@@ -144,9 +136,13 @@ func TestDashboardService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return fmt.Errorf("alert validation error")
origValidateAlerts := validateAlerts
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")
_, err := service.SaveDashboard(dto, false)
@@ -165,23 +161,27 @@ func TestDashboardService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
origUpdateAlerting := UpdateAlerting
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 {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return nil
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
return nil
}
bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
})
dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1}
@@ -200,22 +200,26 @@ func TestDashboardService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
return nil
})
}
bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
origUpdateAlerting := UpdateAlerting
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.SetId(3)
@@ -231,41 +235,42 @@ func TestDashboardService(t *testing.T) {
dto := &SaveDashboardDTO{}
Convey("Should return validation error if dashboard is provisioned", func() {
provisioningValidated := false
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
provisioningValidated = true
cmd.Result = &models.DashboardProvisioning{}
return nil
t.Cleanup(func() {
fakeStore.provisionedData = nil
})
fakeStore.provisionedData = &models.DashboardProvisioning{}
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
validateAlerts = func(dash *models.Dashboard, user *models.SignedInUser) error {
return nil
})
}
bus.AddHandler("test", func(cmd *models.SaveProvisionedDashboardCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
origUpdateAlerting := UpdateAlerting
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.SetId(3)
dto.User = &models.SignedInUser{UserId: 1}
_, err := service.ImportDashboard(dto)
So(provisioningValidated, ShouldBeTrue)
So(err, ShouldEqual, models.ErrDashboardCannotSaveProvisionedDashboard)
})
})
Convey("Given provisioned dashboard", func() {
result := setupDeleteHandlers(true)
result := setupDeleteHandlers(t, &fakeStore, true)
Convey("DeleteProvisionedDashboard should delete it", func() {
err := service.DeleteProvisionedDashboard(1, 1)
@@ -281,7 +286,7 @@ func TestDashboardService(t *testing.T) {
})
Convey("Given non provisioned dashboard", func() {
result := setupDeleteHandlers(false)
result := setupDeleteHandlers(t, &fakeStore, false)
Convey("DeleteProvisionedDashboard should delete it", func() {
err := service.DeleteProvisionedDashboard(1, 1)
@@ -306,15 +311,15 @@ type Result struct {
deleteWasCalled bool
}
func setupDeleteHandlers(provisioned bool) *Result {
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
if provisioned {
cmd.Result = &models.DashboardProvisioning{}
} else {
cmd.Result = nil
}
return nil
func setupDeleteHandlers(t *testing.T, fakeStore *fakeDashboardStore, provisioned bool) *Result {
t.Helper()
t.Cleanup(func() {
fakeStore.provisionedData = nil
})
if provisioned {
fakeStore.provisionedData = &models.DashboardProvisioning{}
}
result := &Result{}
bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error {
@@ -326,3 +331,32 @@ func setupDeleteHandlers(provisioned bool) *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
}

View File

@@ -2,28 +2,32 @@ package dashboards
import (
"errors"
"strings"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
)
// FolderService service for operating on folders
// FolderService is a service for operating on folders.
type FolderService interface {
GetFolders(limit int64) ([]*models.Folder, error)
GetFolderByID(id int64) (*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
DeleteFolder(uid string) (*models.Folder, error)
MakeUserAdmin(orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
}
// NewFolderService factory for creating a new folder service
var NewFolderService = func(orgId int64, user *models.SignedInUser) FolderService {
// NewFolderService is a factory for creating a new folder service.
var NewFolderService = func(orgID int64, user *models.SignedInUser, store dashboards.Store) FolderService {
return &dashboardServiceImpl{
orgId: orgId,
user: user,
orgId: orgID,
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) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: id}
dashFolder, err := getFolder(query)
if err != nil {
return nil, toFolderError(err)
}
@@ -93,8 +96,17 @@ func (dr *dashboardServiceImpl) GetFolderByUID(uid string) (*models.Folder, erro
return dashToFolder(dashFolder), nil
}
func (dr *dashboardServiceImpl) CreateFolder(cmd *models.CreateFolderCommand) error {
dashFolder := cmd.GetDashboardModel(dr.orgId, dr.user.UserId)
func (dr *dashboardServiceImpl) CreateFolder(title, uid string) (*models.Folder, error) {
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{
Dashboard: dashFolder,
@@ -104,23 +116,21 @@ func (dr *dashboardServiceImpl) CreateFolder(cmd *models.CreateFolderCommand) er
saveDashboardCmd, err := dr.buildSaveDashboardCommand(dto, false, false)
if err != nil {
return toFolderError(err)
return nil, toFolderError(err)
}
err = bus.Dispatch(saveDashboardCmd)
dash, err := dr.dashboardStore.SaveDashboard(*saveDashboardCmd)
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)
if err != nil {
return toFolderError(err)
return nil, toFolderError(err)
}
cmd.Result = dashToFolder(dashFolder)
return nil
return dashToFolder(dashFolder), nil
}
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)
}
err = bus.Dispatch(saveDashboardCmd)
dash, err := dr.dashboardStore.SaveDashboard(*saveDashboardCmd)
if err != nil {
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)
if err != nil {
return toFolderError(err)

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert"
@@ -15,8 +16,9 @@ import (
func TestFolderService(t *testing.T) {
Convey("Folder service tests", t, func() {
service := dashboardServiceImpl{
orgId: 1,
user: &models.SignedInUser{UserId: 1},
orgId: 1,
user: &models.SignedInUser{UserId: 1},
dashboardStore: &fakeDashboardStore{},
}
Convey("Given user has no permissions", func() {
@@ -28,32 +30,26 @@ func TestFolderService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return models.ErrDashboardUpdateAccessDenied
origStore := service.dashboardStore
t.Cleanup(func() {
service.dashboardStore = origStore
})
service.dashboardStore = &fakeDashboardStore{
validationError: models.ErrDashboardUpdateAccessDenied,
}
Convey("When get folder by id should return access denied error", func() {
_, err := service.GetFolderByID(1)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
Convey("When get folder by uid should return access denied error", func() {
_, err := service.GetFolderByUID("uid")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
Convey("When creating folder should return access denied error", func() {
err := service.CreateFolder(&models.CreateFolderCommand{
Title: "Folder",
})
So(err, ShouldNotBeNil)
_, err := service.CreateFolder("Folder", "")
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
@@ -62,7 +58,6 @@ func TestFolderService(t *testing.T) {
Uid: "uid",
Title: "Folder",
})
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
@@ -89,18 +84,14 @@ func TestFolderService(t *testing.T) {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
origUpdateAlerting := UpdateAlerting
t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
UpdateAlerting = func(store dashboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
})
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
})
}
bus.AddHandler("test", func(cmd *models.SaveDashboardCommand) error {
cmd.Result = dash
@@ -120,9 +111,7 @@ func TestFolderService(t *testing.T) {
})
Convey("When creating folder should not return access denied error", func() {
err := service.CreateFolder(&models.CreateFolderCommand{
Title: "Folder",
})
_, err := service.CreateFolder("Folder", "")
So(err, ShouldBeNil)
So(provisioningValidated, ShouldBeFalse)
})

View File

@@ -8,7 +8,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"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 {
if err := requirePermissionsOnFolder(c.SignedInUser, cmd.FolderID); err != nil {
if err := lps.requirePermissionsOnFolder(c.SignedInUser, cmd.FolderID); err != nil {
return err
}
if _, err := session.Insert(&libraryPanel); err != nil {
@@ -108,12 +107,22 @@ func (lps *LibraryPanelService) createLibraryPanel(c *models.ReqContext, cmd cre
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)
if err != nil {
return err
}
if err := requirePermissionsOnFolder(user, panel.FolderID); err != nil {
if err := lps.requirePermissionsOnFolder(user, panel.FolderID); err != nil {
return err
}
@@ -126,7 +135,7 @@ func connectDashboard(session *sqlstore.DBSession, dialect migrator.Dialect, use
CreatedBy: user.UserId,
}
if _, err := session.Insert(&libraryPanelDashboard); err != nil {
if dialect.IsUniqueConstraintViolation(err) {
if lps.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
return nil
}
return err
@@ -134,15 +143,6 @@ func connectDashboard(session *sqlstore.DBSession, dialect migrator.Dialect, use
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.
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 {
@@ -151,7 +151,7 @@ func (lps *LibraryPanelService) connectLibraryPanelsForDashboard(c *models.ReqCo
return err
}
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 {
return err
}
@@ -169,7 +169,7 @@ func (lps *LibraryPanelService) deleteLibraryPanel(c *models.ReqContext, uid str
if err != nil {
return err
}
if err := requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil {
if err := lps.requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil {
return err
}
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 {
return err
}
if err := requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil {
if err := lps.requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil {
return err
}
@@ -248,7 +248,7 @@ func (lps *LibraryPanelService) deleteLibraryPanelsInFolder(c *models.ReqContext
}
folderID := folderUIDs[0].ID
if err := requirePermissionsOnFolder(c.SignedInUser, folderID); err != nil {
if err := lps.requirePermissionsOnFolder(c.SignedInUser, folderID); err != nil {
return err
}
var dashIDs []struct {
@@ -496,7 +496,8 @@ func (lps *LibraryPanelService) getLibraryPanelsForDashboardID(c *models.ReqCont
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
if toFolderID == -1 {
toFolderID = fromFolderID
@@ -504,13 +505,13 @@ func handleFolderIDPatches(panelToPatch *LibraryPanel, fromFolderID int64, toFol
// FolderID was provided in the PATCH request
if toFolderID != -1 && toFolderID != fromFolderID {
if err := requirePermissionsOnFolder(user, toFolderID); err != nil {
if err := lps.requirePermissionsOnFolder(user, toFolderID); err != nil {
return err
}
}
// 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
}
@@ -551,7 +552,7 @@ func (lps *LibraryPanelService) patchLibraryPanel(c *models.ReqContext, cmd patc
if cmd.Model == nil {
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
}
if err := syncTitleWithName(&libraryPanel); err != nil {

View File

@@ -10,7 +10,7 @@ func isGeneralFolder(folderID int64) bool {
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) {
return nil
}
@@ -19,7 +19,7 @@ func requirePermissionsOnFolder(user *models.SignedInUser, folderID int64) error
return models.ErrFolderAccessDenied
}
s := dashboards.NewFolderService(user.OrgId, user)
s := dashboards.NewFolderService(user.OrgId, user, lps.SQLStore)
folder, err := s.GetFolderByID(folderID)
if err != nil {
return err

View File

@@ -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",
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)})
resp := sc.service.connectHandler(sc.reqContext)
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)})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())

View File

@@ -25,7 +25,7 @@ func TestPatchLibraryPanel(t *testing.T) {
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
newFolder := createFolderWithACL(t, "NewFolder", sc.user, []folderACLItem{})
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
cmd := patchLibraryPanelCommand{
FolderID: newFolder.Id,
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",
func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, "NewFolder", sc.user, []folderACLItem{})
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
cmd := patchLibraryPanelCommand{
FolderID: newFolder.Id,
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",
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")
resp := sc.service.createHandler(sc.reqContext, command)
var result = validateAndUnMarshalResponse(t, resp)

View File

@@ -68,7 +68,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
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),
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
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),
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")
resp := sc.service.createHandler(sc.reqContext, command)
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
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),
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")
resp := sc.service.createHandler(sc.reqContext, command)
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
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),
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")
resp := sc.service.createHandler(sc.reqContext, cmd)
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),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items)
dashboard := createDashboard(t, sc.user, "Some Folder Dash", folder.Id)
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
dashboard := createDashboard(t, sc.sqlStore, sc.user, "Some Folder Dash", folder.Id)
cmd := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd)
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),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items)
dashboard := createDashboard(t, sc.user, "Some Folder Dash", folder.Id)
folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
dashboard := createDashboard(t, sc.sqlStore, sc.user, "Some Folder Dash", folder.Id)
cmd := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd)
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),
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")
resp := sc.service.createHandler(sc.reqContext, cmd)
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),
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")
resp := sc.service.createHandler(sc.reqContext, command)
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),
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")
resp := sc.service.createHandler(sc.reqContext, command)
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),
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")
resp := sc.service.createHandler(sc.reqContext, cmd)
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),
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")
resp := sc.service.createHandler(sc.reqContext, cmd)
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),
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")
resp := sc.service.createHandler(sc.reqContext, cmd)
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),
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")
resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp)
@@ -329,7 +329,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
func(t *testing.T, sc scenarioContext) {
var results []libraryPanel
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))
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
@@ -401,8 +401,8 @@ func TestLibraryPanelPermissions(t *testing.T) {
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
for i, folderCase := range folderCases {
folder := createFolderWithACL(t, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
dashboard := createDashboard(t, sc.user, "Some Folder Dash", folder.Id)
folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
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)})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@@ -434,7 +434,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
func(t *testing.T, sc scenarioContext) {
var results []libraryPanel
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))
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)

View File

@@ -13,8 +13,8 @@ import (
"gopkg.in/macaron.v1"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"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/registry"
"github.com/grafana/grafana/pkg/services/dashboards"
@@ -731,6 +731,7 @@ type scenarioContext struct {
user models.SignedInUser
folder *models.Folder
initialResult libraryPanelResult
sqlStore *sqlstore.SQLStore
}
type folderACLItem struct {
@@ -738,7 +739,8 @@ type folderACLItem struct {
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.FolderId = folderID
dashItem := &dashboards.SaveDashboardDTO{
@@ -748,53 +750,47 @@ func createDashboard(t *testing.T, user models.SignedInUser, title string, folde
User: &user,
Overwrite: false,
}
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
origUpdateAlerting := dashboards.UpdateAlerting
t.Cleanup(func() {
dashboards.UpdateAlerting = origUpdateAlerting
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
dashboards.UpdateAlerting = func(store dboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
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)
return dashboard
}
func createFolderWithACL(t *testing.T, title string, user models.SignedInUser, items []folderACLItem) *models.Folder {
s := dashboards.NewFolderService(user.OrgId, &user)
folderCmd := models.CreateFolderCommand{
Uid: title,
Title: title,
}
err := s.CreateFolder(&folderCmd)
func createFolderWithACL(t *testing.T, sqlStore *sqlstore.SQLStore, title string, user models.SignedInUser,
items []folderACLItem) *models.Folder {
t.Helper()
s := dashboards.NewFolderService(user.OrgId, &user, sqlStore)
t.Logf("Creating folder with title and UID %q", title)
folder, err := s.CreateFolder(title, title)
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 {
return
}
cmd := models.UpdateDashboardAclCommand{
DashboardID: folderID,
}
var aclItems []*models.DashboardAcl
for _, item := range items {
role := item.roleType
permission := item.permission
cmd.Items = append(cmd.Items, &models.DashboardAcl{
aclItems = append(aclItems, &models.DashboardAcl{
DashboardID: folderID,
Role: &role,
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)
}
func validateAndUnMarshalResponse(t *testing.T, resp response.Response) libraryPanelResult {
t.Helper()
require.Equal(t, 200, resp.Status())
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)) {
t.Helper()
testScenario(t, desc, func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Text - Library Panel")
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
// what to expect in the tests
// In the real world these are identical
cmd := &models.CreateUserCommand{
cmd := models.CreateUserCommand{
Email: "user.in.db@test.com",
Name: "User In DB",
Login: UserInDbName,
}
err := sqlstore.CreateUser(context.Background(), cmd)
_, err := sqlStore.CreateUser(context.Background(), cmd)
require.NoError(t, err)
sc := scenarioContext{
user: user,
ctx: &ctx,
service: &service,
user: user,
ctx: &ctx,
service: &service,
sqlStore: sqlStore,
reqContext: &models.ReqContext{
Context: &ctx,
SignedInUser: &user,
},
}
sc.folder = createFolderWithACL(t, "ScenarioFolder", sc.user, []folderACLItem{})
sc.folder = createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{})
fn(t, sc)
})

View File

@@ -8,7 +8,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"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/services/live/features"
"github.com/grafana/grafana/pkg/setting"
@@ -43,6 +43,7 @@ type GrafanaLive struct {
Cfg *setting.Cfg `inject:""`
RouteRegister routing.RouteRegister `inject:""`
LogsService *cloudwatch.LogsService `inject:""`
PluginManager plugins.Manager `inject:""`
node *centrifuge.Node
// The websocket handler
@@ -222,8 +223,8 @@ func (g *GrafanaLive) GetChannelHandlerFactory(scope string, name string) (model
}, nil
}
p, ok := manager.Plugins[name]
if ok {
p := g.PluginManager.GetPlugin(name)
if p != nil {
h := &PluginHandler{
Plugin: p,
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
func init() {
@@ -21,6 +22,7 @@ var (
type TeamSyncFunc func(user *models.User, externalUser *models.ExternalUserInfo) error
type LoginService struct {
SQLStore *sqlstore.SQLStore `inject:""`
Bus bus.Bus `inject:""`
QuotaService *quota.QuotaService `inject:""`
TeamSync TeamSyncFunc
@@ -107,7 +109,7 @@ func (ls *LoginService) UpsertUser(cmd *models.UpsertUserCommand) error {
// Sync isGrafanaAdmin permission
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
}
}

View File

@@ -6,8 +6,10 @@ import (
"os"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/util/errutil"
)
@@ -22,7 +24,7 @@ type DashboardProvisioner interface {
}
// 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.
type Provisioner struct {
@@ -32,16 +34,15 @@ type Provisioner struct {
}
// 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")
cfgReader := &configReader{path: configDirectory, log: logger}
configs, err := cfgReader.readConfig()
if err != nil {
return nil, errutil.Wrap("Failed to read dashboards config", err)
}
fileReaders, err := getFileReaders(configs, logger)
fileReaders, err := getFileReaders(configs, logger, store)
if err != nil {
return nil, errutil.Wrap("Failed to initialize file readers", err)
}
@@ -115,13 +116,14 @@ func (provider *Provisioner) GetAllowUIUpdatesFromConfig(name string) bool {
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
for _, config := range configs {
switch config.Type {
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 {
return nil, errutil.Wrapf(err, "Failed to create file reader for config %v", config.Name)
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"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/models"
"github.com/grafana/grafana/pkg/services/dashboards"
@@ -35,7 +36,7 @@ type FileReader struct {
}
// 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
path, ok := cfg.Options["path"].(string)
if !ok {
@@ -56,7 +57,7 @@ func NewDashboardFileReader(cfg *config, log log.Logger) (*FileReader, error) {
Cfg: cfg,
Path: path,
log: log,
dashboardProvisioningService: dashboards.NewProvisioningService(),
dashboardProvisioningService: dashboards.NewProvisioningService(store),
FoldersFromFilesStructure: foldersFromFilesStructure,
}, nil
}

View File

@@ -24,7 +24,7 @@ func TestProvisionedSymlinkedFolder(t *testing.T) {
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)
want, err := filepath.Abs(containingID)

View File

@@ -10,6 +10,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/bus"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/util"
@@ -41,14 +42,14 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
Convey("using path parameter", func() {
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(reader.Path, ShouldNotEqual, "")
})
Convey("using folder as options", func() {
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(reader.Path, ShouldNotEqual, "")
})
@@ -56,7 +57,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
Convey("using foldersFromFilesStructure as options", func() {
cfg.Options["path"] = foldersFromFilesStructure
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(reader.Path, ShouldNotEqual, "")
})
@@ -68,7 +69,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
}
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(reader.Path, ShouldEqual, fullPath)
@@ -77,7 +78,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) {
Convey("using relative path", func() {
cfg.Options["folder"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil)
So(err, ShouldBeNil)
resolvedPath := reader.resolvedPath()
@@ -111,7 +112,7 @@ func TestDashboardFileReader(t *testing.T) {
cfg.Options["path"] = defaultDashboards
cfg.Folder = "Team A"
reader, err := NewDashboardFileReader(cfg, logger)
reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil)
err = reader.walkDisk()
@@ -142,7 +143,7 @@ func TestDashboardFileReader(t *testing.T) {
Slug: "grafana",
})
reader, err := NewDashboardFileReader(cfg, logger)
reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil)
err = reader.walkDisk()
@@ -154,7 +155,7 @@ func TestDashboardFileReader(t *testing.T) {
Convey("Overrides id from dashboard.json files", func() {
cfg.Options["path"] = containingID
reader, err := NewDashboardFileReader(cfg, logger)
reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil)
err = reader.walkDisk()
@@ -167,7 +168,7 @@ func TestDashboardFileReader(t *testing.T) {
cfg.Options["path"] = foldersFromFilesStructure
cfg.Options["foldersFromFilesStructure"] = true
reader, err := NewDashboardFileReader(cfg, logger)
reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil)
err = reader.walkDisk()
@@ -211,14 +212,14 @@ func TestDashboardFileReader(t *testing.T) {
Folder: "",
}
_, err := NewDashboardFileReader(cfg, logger)
_, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldNotBeNil)
})
Convey("Broken dashboards should not cause error", func() {
cfg.Options["path"] = brokenDashboards
_, err := NewDashboardFileReader(cfg, logger)
_, err := NewDashboardFileReader(cfg, logger, nil)
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}}
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)
err = reader1.walkDisk()
So(err, ShouldBeNil)
reader2, err := NewDashboardFileReader(cfg2, logger)
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
So(err, ShouldBeNil)
err = reader2.walkDisk()
@@ -336,7 +337,7 @@ func TestDashboardFileReader(t *testing.T) {
Convey("Missing dashboard should be unprovisioned if DisableDeletion = true", func() {
cfg.DisableDeletion = true
reader, err := NewDashboardFileReader(cfg, logger)
reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil)
err = reader.walkDisk()
@@ -347,7 +348,7 @@ func TestDashboardFileReader(t *testing.T) {
})
Convey("Missing dashboard should be deleted if DisableDeletion = false", func() {
reader, err := NewDashboardFileReader(cfg, logger)
reader, err := NewDashboardFileReader(cfg, logger, nil)
So(err, ShouldBeNil)
err = reader.walkDisk()
@@ -395,7 +396,7 @@ func mockDashboardProvisioningService() *fakeDashboardProvisioningService {
mock := fakeDashboardProvisioningService{
provisioned: map[string][]*models.DashboardProvisioning{},
}
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService {
dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
return &mock
}
return &mock

View File

@@ -55,7 +55,7 @@ func (cr *configReaderImpl) readConfig(path string) ([]*pluginsAsConfig, error)
checkOrgIDAndOrgName(apps)
err = validatePluginsConfig(apps)
err = cr.validatePluginsConfig(apps)
if err != nil {
return nil, err
}
@@ -105,7 +105,7 @@ func validateRequiredField(apps []*pluginsAsConfig) error {
return nil
}
func validatePluginsConfig(apps []*pluginsAsConfig) error {
func (cr *configReaderImpl) validatePluginsConfig(apps []*pluginsAsConfig) error {
for i := range apps {
if apps[i].Apps == nil {
continue

View File

@@ -6,11 +6,13 @@ import (
"sync"
"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/services/provisioning/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning/datasources"
"github.com/grafana/grafana/pkg/services/provisioning/notifiers"
"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/util/errutil"
)
@@ -27,10 +29,8 @@ type ProvisioningService interface {
func init() {
registry.Register(&registry.Descriptor{
Name: "ProvisioningService",
Instance: NewProvisioningServiceImpl(
func(path string) (dashboards.DashboardProvisioner, error) {
return dashboards.New(path)
},
Instance: newProvisioningServiceImpl(
dashboards.New,
notifiers.Provision,
datasources.Provision,
plugins.Provision,
@@ -39,7 +39,7 @@ func init() {
})
}
func NewProvisioningServiceImpl(
func newProvisioningServiceImpl(
newDashboardProvisioner dashboards.DashboardProvisionerFactory,
provisionNotifiers func(string) error,
provisionDatasources func(string) error,
@@ -55,7 +55,9 @@ func NewProvisioningServiceImpl(
}
type provisioningServiceImpl struct {
Cfg *setting.Cfg `inject:""`
Cfg *setting.Cfg `inject:""`
RequestHandler plugifaces.DataRequestHandler `inject:""`
SQLStore *sqlstore.SQLStore `inject:""`
log log.Logger
pollingCtxCancel context.CancelFunc
newDashboardProvisioner dashboards.DashboardProvisionerFactory
@@ -134,7 +136,7 @@ func (ps *provisioningServiceImpl) ProvisionNotifications() error {
func (ps *provisioningServiceImpl) ProvisionDashboards() error {
dashboardPath := filepath.Join(ps.Cfg.ProvisioningPath, "dashboards")
dashProvisioner, err := ps.newDashboardProvisioner(dashboardPath)
dashProvisioner, err := ps.newDashboardProvisioner(dashboardPath, ps.SQLStore, ps.RequestHandler)
if err != nil {
return errutil.Wrap("Failed to create provisioner", err)
}

View File

@@ -6,6 +6,8 @@ import (
"testing"
"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/setting"
"github.com/stretchr/testify/assert"
@@ -90,8 +92,8 @@ func setup() *serviceTestStruct {
pollChangesChannel <- ctx
}
serviceTest.service = NewProvisioningServiceImpl(
func(path string) (dashboards.DashboardProvisioner, error) {
serviceTest.service = newProvisioningServiceImpl(
func(string, dboards.Store, plugifaces.DataRequestHandler) (dashboards.DashboardProvisioner, error) {
return serviceTest.mock, nil
},
nil,

View File

@@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -50,6 +49,7 @@ type RenderingService struct {
Cfg *setting.Cfg `inject:""`
RemoteCacheService *remotecache.RemoteCache `inject:""`
PluginManager plugins.Manager `inject:""`
}
func (rs *RenderingService) Init() error {
@@ -87,7 +87,7 @@ func (rs *RenderingService) Run(ctx context.Context) error {
if rs.pluginAvailable() {
rs.log = rs.log.New("renderer", "plugin")
rs.pluginInfo = manager.Renderer
rs.pluginInfo = rs.PluginManager.Renderer()
if err := rs.startPlugin(ctx); err != nil {
return err
@@ -107,7 +107,7 @@ func (rs *RenderingService) Run(ctx context.Context) error {
}
func (rs *RenderingService) pluginAvailable() bool {
return manager.Renderer != nil
return rs.PluginManager.Renderer() != nil
}
func (rs *RenderingService) remoteAvailable() bool {

View File

@@ -2,6 +2,7 @@ package sqlstore
import (
"bytes"
"context"
"fmt"
"strings"
"time"
@@ -166,18 +167,18 @@ func deleteAlertDefinition(dashboardId int64, sess *DBSession) error {
return nil
}
func SaveAlerts(cmd *models.SaveAlertsCommand) error {
return inTransaction(func(sess *DBSession) error {
existingAlerts, err := GetAlertsByDashboardId2(cmd.DashboardId, sess)
func (ss *SQLStore) SaveAlerts(dashID int64, alerts []*models.Alert) error {
return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
existingAlerts, err := GetAlertsByDashboardId2(dashID, sess)
if err != nil {
return err
}
if err := updateAlerts(existingAlerts, cmd, sess); err != nil {
if err := updateAlerts(existingAlerts, alerts, sess); err != nil {
return err
}
if err := deleteMissingAlerts(existingAlerts, cmd, sess); err != nil {
if err := deleteMissingAlerts(existingAlerts, alerts, sess); err != nil {
return err
}
@@ -185,8 +186,27 @@ func SaveAlerts(cmd *models.SaveAlertsCommand) error {
})
}
func updateAlerts(existingAlerts []*models.Alert, cmd *models.SaveAlertsCommand, sess *DBSession) error {
for _, alert := range cmd.Alerts {
func SaveAlerts(cmd *models.SaveAlertsCommand) error {
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
var alertToUpdate *models.Alert
@@ -245,11 +265,11 @@ func updateAlerts(existingAlerts []*models.Alert, cmd *models.SaveAlertsCommand,
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 {
missing := true
for _, k := range cmd.Alerts {
for _, k := range existingAlerts {
if missingAlert.PanelId == k.PanelId {
missing = false
break

View File

@@ -30,10 +30,11 @@ func TestAlertingDataAccess(t *testing.T) {
defer resetTimeNow()
Convey("Testing Alerting data access", t, func() {
InitTestDB(t)
sqlStore := InitTestDB(t)
testDash := insertTestDashboard(t, "dashboard with alerts", 1, 0, false, "alert")
evalData, _ := simplejson.NewJson([]byte(`{"test": "test"}`))
testDash := insertTestDashboard(t, sqlStore, "dashboard with alerts", 1, 0, false, "alert")
evalData, err := simplejson.NewJson([]byte(`{"test": "test"}`))
So(err, ShouldBeNil)
items := []*models.Alert{
{
PanelId: 1,
@@ -54,7 +55,7 @@ func TestAlertingDataAccess(t *testing.T) {
UserId: 1,
}
err := SaveAlerts(&cmd)
err = SaveAlerts(&cmd)
Convey("Can create one alert", func() {
So(err, ShouldBeNil)
@@ -271,10 +272,11 @@ func TestPausingAlerts(t *testing.T) {
defer resetTimeNow()
Convey("Given an alert", t, func() {
InitTestDB(t)
sqlStore := InitTestDB(t)
testDash := insertTestDashboard(t, "dashboard with alerts", 1, 0, false, "alert")
alert, _ := insertTestAlert("Alerting title", "Alerting message", testDash.OrgId, testDash.Id, simplejson.New())
testDash := insertTestDashboard(t, sqlStore, "dashboard with alerts", 1, 0, false, "alert")
alert, err := insertTestAlert("Alerting title", "Alerting message", testDash.OrgId, testDash.Id, simplejson.New())
So(err, ShouldBeNil)
stateDateBeforePause := alert.NewStateDate
stateDateAfterPause := stateDateBeforePause

View File

@@ -1,6 +1,8 @@
package sqlstore
import (
"context"
"fmt"
"strings"
"time"
@@ -24,7 +26,6 @@ var shadowSearchCounter = prometheus.NewCounterVec(
)
func init() {
bus.AddHandler("sql", SaveDashboard)
bus.AddHandler("sql", GetDashboard)
bus.AddHandler("sql", GetDashboards)
bus.AddHandler("sql", DeleteDashboard)
@@ -35,7 +36,6 @@ func init() {
bus.AddHandler("sql", GetDashboardsByPluginId)
bus.AddHandler("sql", GetDashboardPermissionsForUser)
bus.AddHandler("sql", GetDashboardsBySlug)
bus.AddHandler("sql", ValidateDashboardBeforeSave)
bus.AddHandler("sql", HasEditPermissionInFolders)
bus.AddHandler("sql", HasAdminPermissionInFolders)
@@ -44,10 +44,11 @@ func init() {
var generateNewUid func() string = util.GenerateShortUID
func SaveDashboard(cmd *models.SaveDashboardCommand) error {
return inTransaction(func(sess *DBSession) error {
return saveDashboard(sess, cmd)
func (ss *SQLStore) SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) {
err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
return saveDashboard(sess, &cmd)
})
return cmd.Result, err
}
func saveDashboard(sess *DBSession, cmd *models.SaveDashboardCommand) error {
@@ -162,7 +163,7 @@ func saveDashboard(sess *DBSession, cmd *models.SaveDashboardCommand) error {
cmd.Result = dash
return err
return nil
}
func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
@@ -182,6 +183,26 @@ func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
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 {
if query.Id == 0 && len(query.Slug) == 0 && len(query.Uid) == 0 {
return models.ErrDashboardIdentifierNotSet
@@ -573,20 +594,20 @@ func GetDashboardUIDById(query *models.GetDashboardRefByIdQuery) error {
return nil
}
func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *models.ValidateDashboardBeforeSaveCommand) (err error) {
dash := cmd.Dashboard
func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, dash *models.Dashboard, overwrite bool) (bool, error) {
dashWithIdExists := false
isParentFolderChanged := false
var existingById models.Dashboard
if dash.Id > 0 {
var err error
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
if err != nil {
return err
return isParentFolderChanged, fmt.Errorf("SQL query for existing dashboard by ID failed: %w", err)
}
if !dashWithIdExists {
return models.ErrDashboardNotFound
return isParentFolderChanged, models.ErrDashboardNotFound
}
if dash.Uid == "" {
@@ -598,30 +619,32 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *models.Validat
var existingByUid models.Dashboard
if dash.Uid != "" {
var err error
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
if err != nil {
return err
return isParentFolderChanged, fmt.Errorf("SQL query for existing dashboard by UID failed: %w", err)
}
}
if dash.FolderId > 0 {
var existingFolder models.Dashboard
folderExists, folderErr := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId, dialect.BooleanStr(true)).Get(&existingFolder)
if folderErr != nil {
return folderErr
folderExists, err := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId,
dialect.BooleanStr(true)).Get(&existingFolder)
if err != nil {
return isParentFolderChanged, fmt.Errorf("SQL query for folder failed: %w", err)
}
if !folderExists {
return models.ErrDashboardFolderNotFound
return isParentFolderChanged, models.ErrDashboardFolderNotFound
}
}
if !dashWithIdExists && !dashWithUidExists {
return nil
return isParentFolderChanged, nil
}
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
return models.ErrDashboardWithSameUIDExists
return isParentFolderChanged, models.ErrDashboardWithSameUIDExists
}
existing := existingById
@@ -632,84 +655,92 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *models.Validat
existing = existingByUid
if !dash.IsFolder {
cmd.Result.IsParentFolderChanged = true
isParentFolderChanged = true
}
}
if (existing.IsFolder && !dash.IsFolder) ||
(!existing.IsFolder && dash.IsFolder) {
return models.ErrDashboardTypeMismatch
return isParentFolderChanged, models.ErrDashboardTypeMismatch
}
if !dash.IsFolder && dash.FolderId != existing.FolderId {
cmd.Result.IsParentFolderChanged = true
isParentFolderChanged = true
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if cmd.Overwrite {
if overwrite {
dash.SetVersion(existing.Version)
} else {
return models.ErrDashboardVersionMismatch
return isParentFolderChanged, models.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && !cmd.Overwrite {
return models.UpdatePluginDashboardError{PluginId: existing.PluginId}
if existing.PluginId != "" && !overwrite {
return isParentFolderChanged, models.UpdatePluginDashboardError{PluginId: existing.PluginId}
}
return nil
return isParentFolderChanged, nil
}
func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *models.ValidateDashboardBeforeSaveCommand) error {
dash := cmd.Dashboard
func getExistingDashboardByTitleAndFolder(sess *DBSession, dash *models.Dashboard, overwrite,
isParentFolderChanged bool) (bool, error) {
var existing models.Dashboard
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)
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)
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 existing.IsFolder && !dash.IsFolder {
return models.ErrDashboardWithSameNameAsFolder
return isParentFolderChanged, models.ErrDashboardWithSameNameAsFolder
}
if !existing.IsFolder && dash.IsFolder {
return models.ErrDashboardFolderWithSameNameAsDashboard
return isParentFolderChanged, models.ErrDashboardFolderWithSameNameAsDashboard
}
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.SetUid(existing.Uid)
dash.SetVersion(existing.Version)
} else {
return models.ErrDashboardWithSameNameInFolderExists
return isParentFolderChanged, models.ErrDashboardWithSameNameInFolderExists
}
}
return nil
return isParentFolderChanged, nil
}
func ValidateDashboardBeforeSave(cmd *models.ValidateDashboardBeforeSaveCommand) (err error) {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return inTransaction(func(sess *DBSession) error {
if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil {
func (ss *SQLStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
isParentFolderChanged := false
err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
var err error
isParentFolderChanged, err = getExistingDashboardByIdOrUidForUpdate(sess, dashboard, overwrite)
if err != nil {
return err
}
if err = getExistingDashboardByTitleAndFolder(sess, cmd); err != nil {
isParentFolderChanged, err = getExistingDashboardByTitleAndFolder(sess, dashboard, overwrite,
isParentFolderChanged)
if err != nil {
return err
}
return nil
})
if err != nil {
return false, err
}
return isParentFolderChanged, nil
}
func HasEditPermissionInFolders(query *models.HasEditPermissionInFoldersQuery) error {
@@ -719,7 +750,8 @@ func HasEditPermissionInFolders(query *models.HasEditPermissionInFoldersQuery) e
}
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)
type folderCount struct {

View File

@@ -1,24 +1,26 @@
package sqlstore
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", UpdateDashboardAcl)
bus.AddHandler("sql", GetDashboardAclInfoList)
}
func UpdateDashboardAcl(cmd *models.UpdateDashboardAclCommand) error {
return inTransaction(func(sess *DBSession) error {
func (ss *SQLStore) UpdateDashboardACL(dashboardID int64, items []*models.DashboardAcl) error {
return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
// 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 {
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()) {
return models.ErrDashboardAclInfoMissing
}
@@ -35,7 +37,7 @@ func UpdateDashboardAcl(cmd *models.UpdateDashboardAclCommand) error {
// Update dashboard HasAcl flag
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
})
}

View File

@@ -11,14 +11,15 @@ import (
func TestDashboardAclDataAccess(t *testing.T) {
Convey("Testing DB", t, func() {
InitTestDB(t)
sqlStore := InitTestDB(t)
Convey("Given a dashboard folder and a user", func() {
currentUser := createUser(t, "viewer", "Viewer", false)
savedFolder := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod", "webapp")
childDash := insertTestDashboard(t, "2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
currentUser := createUser(t, sqlStore, "viewer", "Viewer", false)
savedFolder := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "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() {
err := testHelperUpdateDashboardAcl(savedFolder.Id, models.DashboardAcl{
err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1,
DashboardID: savedFolder.Id,
Permission: models.PERMISSION_EDIT,
@@ -61,10 +62,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
Convey("Given dashboard folder with removed default permissions", func() {
err := UpdateDashboardAcl(&models.UpdateDashboardAclCommand{
DashboardID: savedFolder.Id,
Items: []*models.DashboardAcl{},
})
err := sqlStore.UpdateDashboardACL(savedFolder.Id, nil)
So(err, ShouldBeNil)
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() {
err := testHelperUpdateDashboardAcl(savedFolder.Id, models.DashboardAcl{
err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1,
UserID: currentUser.Id,
DashboardID: savedFolder.Id,
@@ -97,7 +95,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
Convey("Given child dashboard permission", func() {
err := testHelperUpdateDashboardAcl(childDash.Id, models.DashboardAcl{
err := testHelperUpdateDashboardAcl(t, sqlStore, childDash.Id, models.DashboardAcl{
OrgID: 1,
UserID: currentUser.Id,
DashboardID: childDash.Id,
@@ -121,7 +119,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
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,
UserID: currentUser.Id,
DashboardID: childDash.Id,
@@ -149,7 +147,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
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,
UserID: currentUser.Id,
DashboardID: savedFolder.Id,
@@ -169,7 +167,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email)
Convey("Should be able to delete an existing permission", func() {
err := testHelperUpdateDashboardAcl(savedFolder.Id)
err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id)
So(err, ShouldBeNil)
q3 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1}
@@ -180,14 +178,13 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
Convey("Given a team", func() {
group1 := models.CreateTeamCommand{Name: "group1 name", OrgId: 1}
err := CreateTeam(&group1)
team1, err := sqlStore.CreateTeam("group1 name", "", 1)
So(err, ShouldBeNil)
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,
TeamID: group1.Result.Id,
TeamID: team1.Id,
DashboardID: savedFolder.Id,
Permission: models.PERMISSION_EDIT,
})
@@ -198,13 +195,13 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(err, ShouldBeNil)
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
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() {
err := testHelperUpdateDashboardAcl(savedFolder.Id, models.DashboardAcl{
err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1,
TeamID: group1.Result.Id,
TeamID: team1.Id,
DashboardID: savedFolder.Id,
Permission: models.PERMISSION_ADMIN,
})
@@ -216,7 +213,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(len(q3.Result), ShouldEqual, 1)
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
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)
})
})
})

View File

@@ -7,21 +7,22 @@ import (
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
)
func TestDashboardFolderDataAccess(t *testing.T) {
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() {
folder := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod", "webapp")
dashInRoot := insertTestDashboard(t, "test dash 67", 1, 0, false, "prod", "webapp")
childDash := insertTestDashboard(t, "test dash 23", 1, folder.Id, false, "prod", "webapp")
insertTestDashboard(t, "test dash 45", 1, folder.Id, false, "prod")
folder := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
dashInRoot := insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod", "webapp")
childDash := insertTestDashboard(t, sqlStore, "test dash 23", 1, folder.Id, false, "prod", "webapp")
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("should return all dashboards", func() {
@@ -40,7 +41,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
Convey("and acl is set for dashboard folder", func() {
var otherUser int64 = 999
err := testHelperUpdateDashboardAcl(folder.Id, models.DashboardAcl{
err := testHelperUpdateDashboardAcl(t, sqlStore, folder.Id, models.DashboardAcl{
DashboardID: folder.Id,
OrgID: 1,
UserID: otherUser,
@@ -61,7 +62,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
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,
})
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() {
var otherUser int64 = 999
err := testHelperUpdateDashboardAcl(folder.Id)
err := testHelperUpdateDashboardAcl(t, sqlStore, folder.Id)
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,
})
So(err, ShouldBeNil)
@@ -120,7 +121,9 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
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)
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() {
folder1 := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod")
folder2 := insertTestDashboard(t, "2 test dash folder", 1, 0, true, "prod")
dashInRoot := insertTestDashboard(t, "test dash 67", 1, 0, false, "prod")
childDash1 := insertTestDashboard(t, "child dash 1", 1, folder1.Id, false, "prod")
childDash2 := insertTestDashboard(t, "child dash 2", 1, folder2.Id, false, "prod")
folder1 := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod")
folder2 := insertTestDashboard(t, sqlStore, "2 test dash folder", 1, 0, true, "prod")
dashInRoot := insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod")
childDash1 := insertTestDashboard(t, sqlStore, "child dash 1", 1, folder1.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
Convey("and one folder is expanded, the other collapsed", 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)
So(err, ShouldBeNil)
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() {
var otherUser int64 = 999
err := testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{
const otherUser int64 = 999
err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT,
})
So(err, ShouldBeNil)
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() {
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() {
moveDashboard(1, childDash1.Data, folder2.Id)
moveDashboard(t, sqlStore, 1, childDash1.Data, folder2.Id)
Convey("should return folder without acl and its children", func() {
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() {
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,
})
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() {
query := &search.FindPersistedDashboardsQuery{
@@ -246,13 +255,13 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
Convey("Given two dashboard folders", func() {
folder1 := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod")
folder2 := insertTestDashboard(t, "2 test dash folder", 1, 0, true, "prod")
insertTestDashboard(t, "folder in another org", 2, 0, true, "prod")
folder1 := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod")
folder2 := insertTestDashboard(t, sqlStore, "2 test dash folder", 1, 0, true, "prod")
insertTestDashboard(t, sqlStore, "folder in another org", 2, 0, true, "prod")
adminUser := createUser(t, "admin", "Admin", true)
editorUser := createUser(t, "editor", "Editor", false)
viewerUser := createUser(t, "viewer", "Viewer", false)
adminUser := createUser(t, sqlStore, "admin", "Admin", true)
editorUser := createUser(t, sqlStore, "editor", "Editor", false)
viewerUser := createUser(t, sqlStore, "viewer", "Viewer", false)
Convey("Admin users", 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() {
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,
})
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() {
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,
})
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() {
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,
})
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() {
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,
})
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
}

View File

@@ -1,6 +1,7 @@
package sqlstore
import (
"context"
"errors"
"github.com/grafana/grafana/pkg/bus"
@@ -8,9 +9,6 @@ import (
)
func init() {
bus.AddHandler("sql", GetProvisionedDashboardDataQuery)
bus.AddHandler("sql", SaveProvisionedDashboard)
bus.AddHandler("sql", GetProvisionedDataByDashboardId)
bus.AddHandler("sql", UnprovisionDashboard)
bus.AddHandler("sql", DeleteOrphanedProvisionedDashboards)
}
@@ -22,64 +20,62 @@ type DashboardExtras struct {
Value string
}
func GetProvisionedDataByDashboardId(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
result := &models.DashboardProvisioning{}
exist, err := x.Where("dashboard_id = ?", cmd.DashboardId).Get(result)
func (ss *SQLStore) GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
var data models.DashboardProvisioning
exists, err := x.Where("dashboard_id = ?", dashboardID).Get(&data)
if err != nil {
return err
return nil, err
}
if exist {
cmd.Result = result
if exists {
return &data, nil
}
return nil
return nil, nil
}
func SaveProvisionedDashboard(cmd *models.SaveProvisionedDashboardCommand) error {
return inTransaction(func(sess *DBSession) error {
err := saveDashboard(sess, cmd.DashboardCmd)
if err != nil {
func (ss *SQLStore) SaveProvisionedDashboard(cmd models.SaveDashboardCommand,
provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
if err := saveDashboard(sess, &cmd); err != nil {
return err
}
cmd.Result = cmd.DashboardCmd.Result
if cmd.DashboardProvisioning.Updated == 0 {
cmd.DashboardProvisioning.Updated = cmd.Result.Updated.Unix()
if provisioning.Updated == 0 {
provisioning.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{}
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 {
return err
}
cmd.Id = result.Id
cmd.DashboardId = dashboard.Id
provisioning.Id = result.Id
provisioning.DashboardId = dashboard.Id
if exist {
_, err = sess.ID(result.Id).Update(cmd)
_, err = sess.ID(result.Id).Update(provisioning)
} else {
_, err = sess.Insert(cmd)
_, err = sess.Insert(provisioning)
}
return err
}
func GetProvisionedDashboardDataQuery(cmd *models.GetProvisionedDashboardDataQuery) error {
func (ss *SQLStore) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
var result []*models.DashboardProvisioning
if err := x.Where("name = ?", cmd.Name).Find(&result); err != nil {
return err
if err := ss.engine.Where("name = ?", name).Find(&result); err != nil {
return nil, err
}
cmd.Result = result
return nil
return result, nil
}
// UnprovisionDashboard removes row in dashboard_provisioning for the dashboard making it seem as if manually created.

View File

@@ -13,9 +13,9 @@ import (
func TestDashboardProvisioningTest(t *testing.T) {
Convey("Testing Dashboard provisioning", t, func() {
InitTestDB(t)
sqlStore := InitTestDB(t)
folderCmd := &models.SaveDashboardCommand{
folderCmd := models.SaveDashboardCommand{
OrgId: 1,
FolderId: 0,
IsFolder: true,
@@ -25,13 +25,13 @@ func TestDashboardProvisioningTest(t *testing.T) {
}),
}
err := SaveDashboard(folderCmd)
dash, err := sqlStore.SaveDashboard(folderCmd)
So(err, ShouldBeNil)
saveDashboardCmd := &models.SaveDashboardCommand{
saveDashboardCmd := models.SaveDashboardCommand{
OrgId: 1,
IsFolder: false,
FolderId: folderCmd.Result.Id,
FolderId: dash.Id,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": "test dashboard",
@@ -41,43 +41,38 @@ func TestDashboardProvisioningTest(t *testing.T) {
Convey("Saving dashboards with provisioning meta data", func() {
now := time.Now()
cmd := &models.SaveProvisionedDashboardCommand{
DashboardCmd: saveDashboardCmd,
DashboardProvisioning: &models.DashboardProvisioning{
Name: "default",
ExternalId: "/var/grafana.json",
Updated: now.Unix(),
},
provisioning := &models.DashboardProvisioning{
Name: "default",
ExternalId: "/var/grafana.json",
Updated: now.Unix(),
}
err := SaveProvisionedDashboard(cmd)
dash, err := sqlStore.SaveProvisionedDashboard(saveDashboardCmd, provisioning)
So(err, ShouldBeNil)
So(cmd.Result, ShouldNotBeNil)
So(cmd.Result.Id, ShouldNotEqual, 0)
dashId := cmd.Result.Id
So(dash, ShouldNotBeNil)
So(dash.Id, ShouldNotEqual, 0)
dashId := dash.Id
Convey("Deleting orphaned provisioned dashboards", func() {
anotherCmd := &models.SaveProvisionedDashboardCommand{
DashboardCmd: &models.SaveDashboardCommand{
OrgId: 1,
IsFolder: false,
FolderId: folderCmd.Result.Id,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": "another_dashboard",
}),
},
DashboardProvisioning: &models.DashboardProvisioning{
Name: "another_reader",
ExternalId: "/var/grafana.json",
Updated: now.Unix(),
},
saveCmd := models.SaveDashboardCommand{
OrgId: 1,
IsFolder: false,
FolderId: dash.Id,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": "another_dashboard",
}),
}
provisioning := &models.DashboardProvisioning{
Name: "another_reader",
ExternalId: "/var/grafana.json",
Updated: now.Unix(),
}
err := SaveProvisionedDashboard(anotherCmd)
anotherDash, err := sqlStore.SaveProvisionedDashboard(saveCmd, provisioning)
So(err, ShouldBeNil)
query := &models.GetDashboardsQuery{DashboardIds: []int64{anotherCmd.Result.Id}}
query := &models.GetDashboardsQuery{DashboardIds: []int64{anotherDash.Id}}
err = GetDashboards(query)
So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil)
@@ -85,7 +80,7 @@ func TestDashboardProvisioningTest(t *testing.T) {
deleteCmd := &models.DeleteOrphanedProvisionedDashboardsCommand{ReaderNames: []string{"default"}}
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)
So(err, ShouldBeNil)
@@ -94,45 +89,38 @@ func TestDashboardProvisioningTest(t *testing.T) {
})
Convey("Can query for provisioned dashboards", func() {
query := &models.GetProvisionedDashboardDataQuery{Name: "default"}
err := GetProvisionedDashboardDataQuery(query)
rslt, err := sqlStore.GetProvisionedDashboardData("default")
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].DashboardId, ShouldEqual, dashId)
So(query.Result[0].Updated, ShouldEqual, now.Unix())
So(len(rslt), ShouldEqual, 1)
So(rslt[0].DashboardId, ShouldEqual, dashId)
So(rslt[0].Updated, ShouldEqual, now.Unix())
})
Convey("Can query for one provisioned dashboard", func() {
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: cmd.Result.Id}
err := GetProvisionedDataByDashboardId(query)
data, err := sqlStore.GetProvisionedDataByDashboardID(dash.Id)
So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil)
So(data, ShouldNotBeNil)
})
Convey("Can query for none provisioned dashboard", func() {
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: 3000}
err := GetProvisionedDataByDashboardId(query)
data, err := sqlStore.GetProvisionedDataByDashboardID(3000)
So(err, ShouldBeNil)
So(query.Result, ShouldBeNil)
So(data, ShouldBeNil)
})
Convey("Deleting folder should delete provision meta data", func() {
deleteCmd := &models.DeleteDashboardCommand{
Id: folderCmd.Result.Id,
Id: dash.Id,
OrgId: 1,
}
So(DeleteDashboard(deleteCmd), ShouldBeNil)
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: cmd.Result.Id}
err = GetProvisionedDataByDashboardId(query)
data, err := sqlStore.GetProvisionedDataByDashboardID(dash.Id)
So(err, ShouldBeNil)
So(query.Result, ShouldBeNil)
So(data, ShouldBeNil)
})
Convey("UnprovisionDashboard should delete provisioning metadata", func() {
@@ -142,11 +130,9 @@ func TestDashboardProvisioningTest(t *testing.T) {
So(UnprovisionDashboard(unprovisionCmd), ShouldBeNil)
query := &models.GetProvisionedDashboardDataByIdQuery{DashboardId: dashId}
err = GetProvisionedDataByDashboardId(query)
data, err := sqlStore.GetProvisionedDataByDashboardID(dashId)
So(err, ShouldBeNil)
So(query.Result, ShouldBeNil)
So(data, ShouldBeNil)
})
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -22,13 +22,13 @@ import (
func TestDashboardDataAccess(t *testing.T) {
Convey("Testing DB", t, func() {
InitTestDB(t)
sqlStore := InitTestDB(t)
Convey("Given saved dashboard", func() {
savedFolder := insertTestDashboard(t, "1 test dash folder", 1, 0, true, "prod", "webapp")
savedDash := insertTestDashboard(t, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
insertTestDashboard(t, "test dash 45", 1, savedFolder.Id, false, "prod")
insertTestDashboard(t, "test dash 67", 1, 0, false, "prod", "webapp")
savedFolder := insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
savedDash := insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
insertTestDashboard(t, sqlStore, "test dash 45", 1, savedFolder.Id, false, "prod")
insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod", "webapp")
Convey("Should return dashboard model", func() {
So(savedDash.Title, ShouldEqual, "test dash 23")
@@ -104,7 +104,7 @@ func TestDashboardDataAccess(t *testing.T) {
})
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{
Id: dash.Id,
@@ -129,8 +129,7 @@ func TestDashboardDataAccess(t *testing.T) {
"tags": []interface{}{},
}),
}
err := SaveDashboard(&cmd)
_, err := sqlStore.SaveDashboard(cmd)
So(err, ShouldBeNil)
generateNewUid = util.GenerateShortUID
@@ -145,13 +144,12 @@ func TestDashboardDataAccess(t *testing.T) {
}),
UserId: 100,
}
err := SaveDashboard(&cmd)
dashboard, err := sqlStore.SaveDashboard(cmd)
So(err, ShouldBeNil)
So(cmd.Result.CreatedBy, ShouldEqual, 100)
So(cmd.Result.Created.IsZero(), ShouldBeFalse)
So(cmd.Result.UpdatedBy, ShouldEqual, 100)
So(cmd.Result.Updated.IsZero(), ShouldBeFalse)
So(dashboard.CreatedBy, ShouldEqual, 100)
So(dashboard.Created.IsZero(), ShouldBeFalse)
So(dashboard.UpdatedBy, ShouldEqual, 100)
So(dashboard.Updated.IsZero(), ShouldBeFalse)
})
Convey("Should be able to update dashboard by id and remove folderId", func() {
@@ -166,10 +164,9 @@ func TestDashboardDataAccess(t *testing.T) {
FolderId: 2,
UserId: 100,
}
err := SaveDashboard(&cmd)
dash, err := sqlStore.SaveDashboard(cmd)
So(err, ShouldBeNil)
So(cmd.Result.FolderId, ShouldEqual, 2)
So(dash.FolderId, ShouldEqual, 2)
cmd = models.SaveDashboardCommand{
OrgId: 1,
@@ -182,8 +179,7 @@ func TestDashboardDataAccess(t *testing.T) {
Overwrite: true,
UserId: 100,
}
err = SaveDashboard(&cmd)
_, err = sqlStore.SaveDashboard(cmd)
So(err, ShouldBeNil)
query := models.GetDashboardQuery{
@@ -201,7 +197,7 @@ func TestDashboardDataAccess(t *testing.T) {
})
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}
err := DeleteDashboard(deleteCmd)
@@ -236,7 +232,7 @@ func TestDashboardDataAccess(t *testing.T) {
}),
}
err := SaveDashboard(&cmd)
_, err := sqlStore.SaveDashboard(cmd)
So(err, ShouldEqual, models.ErrDashboardNotFound)
})
@@ -250,8 +246,7 @@ func TestDashboardDataAccess(t *testing.T) {
"tags": []interface{}{},
}),
}
err := SaveDashboard(&cmd)
_, err := sqlStore.SaveDashboard(cmd)
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() {
starredDash := insertTestDashboard(t, "starred dash", 1, 0, false)
starredDash := insertTestDashboard(t, sqlStore, "starred dash", 1, 0, false)
err := StarDashboard(&models.StarDashboardCommand{
DashboardId: starredDash.Id,
UserId: 10,
@@ -396,9 +391,9 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Given a plugin with imported dashboards", func() {
pluginId := "test-app"
appFolder := insertTestDashboardForPlugin("app-test", 1, 0, true, pluginId)
insertTestDashboardForPlugin("app-dash1", 1, appFolder.Id, false, pluginId)
insertTestDashboardForPlugin("app-dash2", 1, appFolder.Id, false, pluginId)
appFolder := insertTestDashboardForPlugin(t, sqlStore, "app-test", 1, 0, true, pluginId)
insertTestDashboardForPlugin(t, sqlStore, "app-dash1", 1, appFolder.Id, false, pluginId)
insertTestDashboardForPlugin(t, sqlStore, "app-dash2", 1, appFolder.Id, false, pluginId)
Convey("Should return imported dashboard", func() {
query := models.GetDashboardsByPluginIdQuery{
@@ -417,9 +412,9 @@ func TestDashboardDataAccess(t *testing.T) {
func TestDashboard_SortingOptions(t *testing.T) {
// insertTestDashboard uses GoConvey's assertions. Workaround.
Convey("test with multiple sorting options", t, func() {
InitTestDB(t)
dashB := insertTestDashboard(t, "Beta", 1, 0, false)
dashA := insertTestDashboard(t, "Alfa", 1, 0, false)
sqlStore := InitTestDB(t)
dashB := insertTestDashboard(t, sqlStore, "Beta", 1, 0, false)
dashA := insertTestDashboard(t, sqlStore, "Alfa", 1, 0, false)
assert.NotZero(t, 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()
cmd := models.SaveDashboardCommand{
@@ -454,17 +450,20 @@ func insertTestDashboard(t *testing.T, title string, orgId int64, folderId int64
"tags": tags,
}),
}
err := SaveDashboard(&cmd)
dash, err := sqlStore.SaveDashboard(cmd)
require.NoError(t, err)
require.NotNil(t, dash)
cmd.Result.Data.Set("id", cmd.Result.Id)
cmd.Result.Data.Set("uid", cmd.Result.Uid)
dash.Data.Set("id", dash.Id)
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{
OrgId: orgId,
FolderId: folderId,
@@ -476,13 +475,13 @@ func insertTestDashboardForPlugin(title string, orgId int64, folderId int64, isF
PluginId: pluginId,
}
err := SaveDashboard(&cmd)
dash, err := sqlStore.SaveDashboard(cmd)
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()
setting.AutoAssignOrg = true
@@ -490,27 +489,13 @@ func createUser(t *testing.T, name string, role string, isAdmin bool) models.Use
setting.AutoAssignOrgRole = role
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)
q1 := models.GetUserOrgListQuery{UserId: currentUserCmd.Result.Id}
q1 := models.GetUserOrgListQuery{UserId: currentUser.Id}
err = GetUserOrgList(&q1)
require.NoError(t, err)
require.Equal(t, models.RoleType(role), q1.Result[0].Role)
return currentUserCmd.Result
}
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
return *currentUser
}

View File

@@ -13,7 +13,9 @@ import (
"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
saveCmd := models.SaveDashboardCommand{
@@ -21,17 +23,16 @@ func updateTestDashboard(dashboard *models.Dashboard, data map[string]interface{
Overwrite: true,
Dashboard: simplejson.NewFromAny(data),
}
err := SaveDashboard(&saveCmd)
_, err := sqlStore.SaveDashboard(saveCmd)
So(err, ShouldBeNil)
}
func TestGetDashboardVersion(t *testing.T) {
Convey("Testing dashboard version retrieval", t, func() {
InitTestDB(t)
sqlStore := InitTestDB(t)
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{
DashboardId: savedDash.Id,
@@ -71,8 +72,8 @@ func TestGetDashboardVersion(t *testing.T) {
func TestGetDashboardVersions(t *testing.T) {
Convey("Testing dashboard versions retrieval", t, func() {
InitTestDB(t)
savedDash := insertTestDashboard(t, "test dash 43", 1, 0, false, "diff-all")
sqlStore := InitTestDB(t)
savedDash := insertTestDashboard(t, sqlStore, "test dash 43", 1, 0, false, "diff-all")
Convey("Get all versions for a given Dashboard ID", func() {
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() {
updateTestDashboard(savedDash, map[string]interface{}{
updateTestDashboard(t, sqlStore, savedDash, map[string]interface{}{
"tags": "different-tag",
})
@@ -107,14 +108,14 @@ func TestGetDashboardVersions(t *testing.T) {
func TestDeleteExpiredVersions(t *testing.T) {
Convey("Testing dashboard versions clean up", t, func() {
InitTestDB(t)
sqlStore := InitTestDB(t)
versionsToKeep := 5
versionsToWrite := 10
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++ {
updateTestDashboard(savedDash, map[string]interface{}{
updateTestDashboard(t, sqlStore, savedDash, map[string]interface{}{
"tags": "different-tag",
})
}
@@ -152,7 +153,7 @@ func TestDeleteExpiredVersions(t *testing.T) {
versionsToWriteBigNumber := perBatch*maxBatches + versionsToWrite
for i := 0; i < versionsToWriteBigNumber-versionsToWrite; i++ {
updateTestDashboard(savedDash, map[string]interface{}{
updateTestDashboard(t, sqlStore, savedDash, map[string]interface{}{
"tags": "different-tag",
})
}

View File

@@ -15,7 +15,7 @@ import (
func TestAccountDataAccess(t *testing.T) {
Convey("Testing Account DB Access", t, func() {
InitTestDB(t)
sqlStore := InitTestDB(t)
Convey("Given we have organizations, we can query them by IDs", func() {
var err error
@@ -78,13 +78,13 @@ func TestAccountDataAccess(t *testing.T) {
ac1cmd := models.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 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)
err = CreateUser(context.Background(), &ac2cmd)
ac2, err := sqlStore.CreateUser(context.Background(), ac2cmd)
So(err, ShouldBeNil)
q1 := models.GetUserOrgListQuery{UserId: ac1cmd.Result.Id}
q2 := models.GetUserOrgListQuery{UserId: ac2cmd.Result.Id}
q1 := models.GetUserOrgListQuery{UserId: ac1.Id}
q2 := models.GetUserOrgListQuery{UserId: ac2.Id}
err = GetUserOrgList(&q1)
So(err, ShouldBeNil)
err = GetUserOrgList(&q2)
@@ -101,13 +101,10 @@ func TestAccountDataAccess(t *testing.T) {
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}
err := CreateUser(context.Background(), &ac1cmd)
err = CreateUser(context.Background(), &ac2cmd)
ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
ac2, err := sqlStore.CreateUser(context.Background(), ac2cmd)
So(err, ShouldBeNil)
ac1 := ac1cmd.Result
ac2 := ac2cmd.Result
Convey("Should be able to read user info projection", func() {
query := models.GetUserProfileQuery{UserId: ac1.Id}
err = GetUserProfile(&query)
@@ -266,9 +263,8 @@ func TestAccountDataAccess(t *testing.T) {
Convey("Given an org user with dashboard permissions", func() {
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)
ac3 := ac3cmd.Result
orgUserCmd := models.AddOrgUserCommand{
OrgId: ac1.OrgId,
@@ -284,13 +280,17 @@ func TestAccountDataAccess(t *testing.T) {
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 3)
dash1 := insertTestDashboard(t, "1 test dash", ac1.OrgId, 0, false, "prod", "webapp")
dash2 := insertTestDashboard(t, "2 test dash", ac3.OrgId, 0, false, "prod", "webapp")
dash1 := insertTestDashboard(t, sqlStore, "1 test dash", ac1.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)
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)
Convey("When org user is deleted", func() {
@@ -322,13 +322,16 @@ func TestAccountDataAccess(t *testing.T) {
})
}
func testHelperUpdateDashboardAcl(dashboardId int64, items ...models.DashboardAcl) error {
cmd := models.UpdateDashboardAclCommand{DashboardID: dashboardId}
for _, i := range items {
item := i
func testHelperUpdateDashboardAcl(t *testing.T, sqlStore *SQLStore, dashboardID int64,
items ...models.DashboardAcl) error {
t.Helper()
var itemPtrs []*models.DashboardAcl
for _, it := range items {
item := it
item.Created = time.Now()
item.Updated = time.Now()
cmd.Items = append(cmd.Items, &item)
itemPtrs = append(itemPtrs, &item)
}
return UpdateDashboardAcl(&cmd)
return sqlStore.UpdateDashboardACL(dashboardID, itemPtrs)
}

View File

@@ -10,25 +10,27 @@ import (
)
func init() {
bus.AddHandler("sql", GetPluginSettings)
bus.AddHandler("sql", GetPluginSettingById)
bus.AddHandler("sql", UpdatePluginSetting)
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
FROM plugin_setting `
params := make([]interface{}, 0)
if query.OrgId != 0 {
if orgID != 0 {
sql += "WHERE org_id=?"
params = append(params, query.OrgId)
params = append(params, orgID)
}
sess := x.SQL(sql, params...)
query.Result = make([]*models.PluginSettingInfoDTO, 0)
return sess.Find(&query.Result)
var rslt []*models.PluginSettingInfoDTO
if err := sess.Find(&rslt); err != nil {
return nil, err
}
return rslt, nil
}
func GetPluginSettingById(query *models.GetPluginSettingByIdQuery) error {

View File

@@ -5,7 +5,6 @@ package searchstore_test
import (
"context"
"fmt"
"testing"
"time"
@@ -34,13 +33,11 @@ func TestBuilder_EqualResults_Basic(t *testing.T) {
}
db := setupTestEnvironment(t)
err := createDashboards(0, 1, user.OrgId)
require.NoError(t, err)
createDashboards(t, db, 0, 1, user.OrgId)
// create one dashboard in another organization that shouldn't
// be listed in the results.
err = createDashboards(1, 2, 2)
require.NoError(t, err)
createDashboards(t, db, 1, 2, 2)
builder := &searchstore.Builder{
Filters: []interface{}{
@@ -51,7 +48,7 @@ func TestBuilder_EqualResults_Basic(t *testing.T) {
}
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)
return sess.SQL(sql, params...).Find(&res)
})
@@ -77,8 +74,7 @@ func TestBuilder_Pagination(t *testing.T) {
}
db := setupTestEnvironment(t)
err := createDashboards(0, 25, user.OrgId)
require.NoError(t, err)
createDashboards(t, db, 0, 25, user.OrgId)
builder := &searchstore.Builder{
Filters: []interface{}{
@@ -91,7 +87,7 @@ func TestBuilder_Pagination(t *testing.T) {
resPg1 := []sqlstore.DashboardSearchProjection{}
resPg2 := []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)
err := sess.SQL(sql, params...).Find(&resPg1)
if err != nil {
@@ -124,8 +120,7 @@ func TestBuilder_Permissions(t *testing.T) {
}
db := setupTestEnvironment(t)
err := createDashboards(0, 1, user.OrgId)
require.NoError(t, err)
createDashboards(t, db, 0, 1, user.OrgId)
level := models.PERMISSION_EDIT
@@ -145,7 +140,7 @@ func TestBuilder_Permissions(t *testing.T) {
}
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)
return sess.SQL(sql, params...).Find(&res)
})
@@ -161,10 +156,10 @@ func setupTestEnvironment(t *testing.T) *sqlstore.SQLStore {
return store
}
func createDashboards(startID, endID int, orgID int64) error {
if endID < startID {
return fmt.Errorf("startID must be smaller than endID")
}
func createDashboards(t *testing.T, db *sqlstore.SQLStore, startID, endID int, orgID int64) {
t.Helper()
require.GreaterOrEqual(t, endID, startID)
for i := startID; i < endID; i++ {
dashboard, err := simplejson.NewJson([]byte(`{
@@ -176,20 +171,15 @@ func createDashboards(startID, endID int, orgID int64) error {
"schemaVersion": 16,
"version": 0
}`))
if err != nil {
return err
}
err = sqlstore.SaveDashboard(&models.SaveDashboardCommand{
require.NoError(t, err)
_, err = db.SaveDashboard(models.SaveDashboardCommand{
Dashboard: dashboard,
UserId: 1,
OrgId: orgID,
UpdatedAt: time.Now(),
})
if err != nil {
return err
}
require.NoError(t, err)
}
return nil
}
// lexiCounter counts in a lexicographically sortable order.

View File

@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSQLBuilder(t *testing.T) {
@@ -138,8 +139,8 @@ func TestSQLBuilder(t *testing.T) {
})
}
var shouldFind = true
var shouldNotFind = false
const shouldFind = true
const shouldNotFind = false
type DashboardProps struct {
OrgId int64
@@ -164,38 +165,35 @@ type dashboardResponse struct {
}
func test(t *testing.T, dashboardProps DashboardProps, dashboardPermission *DashboardPermission, search Search, shouldFind bool) {
// Will also cleanup the db
sqlStore := InitTestDB(t)
t.Helper()
dashboard, err := createDummyDashboard(dashboardProps)
if !assert.Equal(t, nil, err) {
return
}
t.Run("", func(t *testing.T) {
// Will also cleanup the db
sqlStore := InitTestDB(t)
var aclUserId int64
if dashboardPermission != nil {
aclUserId, err = createDummyAcl(dashboardPermission, search, dashboard.Id)
if !assert.Equal(t, nil, err) {
return
dashboard := createDummyDashboard(t, sqlStore, dashboardProps)
var aclUserID int64
if dashboardPermission != nil {
aclUserID = createDummyACL(t, sqlStore, dashboardPermission, search, dashboard.Id)
t.Logf("Created ACL with user ID %d\n", aclUserID)
}
}
dashboards, err := getDashboards(sqlStore, search, aclUserId)
if !assert.Equal(t, nil, err) {
return
}
dashboards := getDashboards(t, sqlStore, search, aclUserID)
if shouldFind {
if assert.Equal(t, 1, len(dashboards), "Should return one dashboard") {
assert.Equal(t, dashboards[0].Id, dashboard.Id, "Should return created dashboard")
if shouldFind {
require.Len(t, dashboards, 1, "Should return one 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))
createUserCmd := &models.CreateUserCommand{
createUserCmd := models.CreateUserCommand{
Email: uid + "@example.com",
Login: uid,
Name: uid,
@@ -207,33 +205,28 @@ func createDummyUser() (*models.User, error) {
SkipOrgSetup: false,
DefaultOrgRole: string(models.ROLE_VIEWER),
}
err := CreateUser(context.Background(), createUserCmd)
if err != nil {
return nil, err
}
user, err := sqlStore.CreateUser(context.Background(), createUserCmd)
require.NoError(t, err)
return &createUserCmd.Result, nil
return user
}
func createDummyTeam() (*models.Team, error) {
cmd := &models.CreateTeamCommand{
// Does not matter in this tests actually
OrgId: 1,
Name: "test",
Email: "test@example.com",
}
err := CreateTeam(cmd)
if err != nil {
return nil, err
}
func createDummyTeam(t *testing.T, sqlStore *SQLStore) models.Team {
t.Helper()
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) {
json, _ := simplejson.NewJson([]byte(`{"schemaVersion":17,"title":"gdev dashboards","uid":"","version":1}`))
func createDummyDashboard(t *testing.T, sqlStore *SQLStore, dashboardProps DashboardProps) *models.Dashboard {
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,
UserId: 0,
Overwrite: false,
@@ -250,53 +243,40 @@ func createDummyDashboard(dashboardProps DashboardProps) (*models.Dashboard, err
saveDashboardCmd.OrgId = 1
}
err := SaveDashboard(saveDashboardCmd)
if err != nil {
return nil, err
}
dash, err := sqlStore.SaveDashboard(saveDashboardCmd)
require.NoError(t, 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{
OrgID: 1,
Created: time.Now(),
Updated: time.Now(),
Permission: dashboardPermission.Permission,
DashboardID: dashboardId,
DashboardID: dashboardID,
}
var user *models.User
var err error
if dashboardPermission.User {
user, err = createDummyUser()
if err != nil {
return 0, err
}
t.Logf("Creating user")
user = createDummyUser(t, sqlStore)
acl.UserID = user.Id
}
if dashboardPermission.Team {
team, err := createDummyTeam()
if err != nil {
return 0, err
}
t.Logf("Creating team")
team := createDummyTeam(t, sqlStore)
if search.UserFromACL {
user, err = createDummyUser()
if err != nil {
return 0, err
}
addTeamMemberCmd := &models.AddTeamMemberCommand{
UserId: user.Id,
OrgId: 1,
TeamId: team.Id,
}
err = AddTeamMember(addTeamMemberCmd)
if err != nil {
return 0, err
}
user = createDummyUser(t, sqlStore)
err := sqlStore.AddTeamMember(user.Id, 1, team.Id, false, 0)
require.NoError(t, err)
t.Logf("Created team member with ID %d", user.Id)
}
acl.TeamID = team.Id
@@ -306,18 +286,17 @@ func createDummyAcl(dashboardPermission *DashboardPermission, search Search, das
acl.Role = &dashboardPermission.Role
}
updateAclCmd := &models.UpdateDashboardAclCommand{
DashboardID: dashboardId,
Items: []*models.DashboardAcl{acl},
}
err = UpdateDashboardAcl(updateAclCmd)
err := sqlStore.UpdateDashboardACL(dashboardID, []*models.DashboardAcl{acl})
require.NoError(t, err)
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{}
signedInUser := &models.SignedInUser{
UserId: 9999999999,
@@ -335,12 +314,14 @@ func getDashboards(sqlStore *SQLStore, search Search, aclUserId int64) ([]*dashb
signedInUser.OrgRole = models.ROLE_VIEWER
}
if search.UserFromACL {
signedInUser.UserId = aclUserId
signedInUser.UserId = aclUserID
}
var res []*dashboardResponse
builder.Write("SELECT * FROM dashboard WHERE true")
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)
return res, err
require.NoError(t, err)
return res
}

View File

@@ -22,7 +22,6 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"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/errutil"
_ "github.com/lib/pq"

View File

@@ -13,8 +13,8 @@ import (
)
func TestStatsDataAccess(t *testing.T) {
InitTestDB(t)
populateDB(t)
sqlStore := InitTestDB(t)
populateDB(t, sqlStore)
t.Run("Get system stats should not results in error", func(t *testing.T) {
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)
for i := range users {
cmd := &models.CreateUserCommand{
cmd := models.CreateUserCommand{
Email: fmt.Sprintf("usertest%v@test.com", i),
Name: fmt.Sprintf("user name %v", i),
Login: fmt.Sprintf("user_test_%v_login", i),
OrgName: fmt.Sprintf("Org #%v", i),
}
err := CreateUser(context.Background(), cmd)
user, err := sqlStore.CreateUser(context.Background(), cmd)
require.NoError(t, err)
users[i] = cmd.Result
users[i] = *user
}
// get 1st user's organisation

View File

@@ -2,6 +2,7 @@ package sqlstore
import (
"bytes"
"context"
"fmt"
"strings"
"time"
@@ -11,14 +12,12 @@ import (
)
func init() {
bus.AddHandler("sql", CreateTeam)
bus.AddHandler("sql", UpdateTeam)
bus.AddHandler("sql", DeleteTeam)
bus.AddHandler("sql", SearchTeams)
bus.AddHandler("sql", GetTeamById)
bus.AddHandler("sql", GetTeamsByUser)
bus.AddHandler("sql", AddTeamMember)
bus.AddHandler("sql", UpdateTeamMember)
bus.AddHandler("sql", RemoveTeamMember)
bus.AddHandler("sql", GetTeamMembers)
@@ -43,7 +42,7 @@ func getFilteredUsers(signedInUser *models.SignedInUser, hiddenUsers map[string]
func getTeamMemberCount(filteredUsers []string) string {
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
WHERE team_member.team_id = team.id AND ` + dialect.Quote("user") + `.login NOT IN (?` +
strings.Repeat(",?", len(filteredUsers)-1) + ")" +
@@ -75,28 +74,25 @@ func getTeamSelectSQLBase(filteredUsers []string) string {
` FROM team as team `
}
func CreateTeam(cmd *models.CreateTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, 0, sess); err != nil {
func (ss *SQLStore) CreateTeam(name, email string, orgID int64) (models.Team, error) {
team := models.Team{
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
} else if isNameTaken {
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)
cmd.Result = team
return err
})
return team, err
}
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) {
if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", orgId, teamId); err != nil {
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 {
return false, err
} else if len(res) != 1 {
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) {
var team models.Team
exists, err := sess.Where("org_id=? and name=?", orgId, name).Get(&team)
if err != nil {
return false, nil
}
@@ -283,26 +278,27 @@ func GetTeamsByUser(query *models.GetTeamsByUserQuery) error {
}
// AddTeamMember adds a user to a team
func AddTeamMember(cmd *models.AddTeamMemberCommand) error {
return inTransaction(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 {
func (ss *SQLStore) AddTeamMember(userID, orgID, teamID int64, isExternal bool, permission models.PermissionType) 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=?",
orgID, teamID, userID); err != nil {
return err
} else if len(res) == 1 {
return models.ErrTeamMemberAlreadyAdded
}
if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
if _, err := teamExists(orgID, teamID, sess); err != nil {
return err
}
entity := models.TeamMember{
OrgId: cmd.OrgId,
TeamId: cmd.TeamId,
UserId: cmd.UserId,
External: cmd.External,
OrgId: orgID,
TeamId: teamID,
UserId: userID,
External: isExternal,
Created: time.Now(),
Updated: time.Now(),
Permission: cmd.Permission,
Permission: permission,
}
_, err := sess.Insert(&entity)

View File

@@ -14,32 +14,29 @@ import (
func TestTeamCommandsAndQueries(t *testing.T) {
Convey("Testing Team commands & queries", t, func() {
InitTestDB(t)
sqlStore := InitTestDB(t)
Convey("Given saved users and two teams", func() {
var userIds []int64
for i := 0; i < 5; i++ {
userCmd := &models.CreateUserCommand{
userCmd := models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
}
err := CreateUser(context.Background(), userCmd)
user, err := sqlStore.CreateUser(context.Background(), userCmd)
So(err, ShouldBeNil)
userIds = append(userIds, userCmd.Result.Id)
userIds = append(userIds, user.Id)
}
var testOrgId int64 = 1
group1 := models.CreateTeamCommand{OrgId: testOrgId, Name: "group1 name", Email: "test1@test.com"}
group2 := models.CreateTeamCommand{OrgId: testOrgId, Name: "group2 name", Email: "test2@test.com"}
err := CreateTeam(&group1)
const testOrgID int64 = 1
team1, err := sqlStore.CreateTeam("group1 name", "test1@test.com", testOrgID)
So(err, ShouldBeNil)
err = CreateTeam(&group2)
team2, err := sqlStore.CreateTeam("group2 name", "test2@test.com", testOrgID)
So(err, ShouldBeNil)
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)
So(err, ShouldBeNil)
So(query.Page, ShouldEqual, 1)
@@ -47,33 +44,33 @@ func TestTeamCommandsAndQueries(t *testing.T) {
team1 := query.Result.Teams[0]
So(team1.Name, ShouldEqual, "group1 name")
So(team1.Email, ShouldEqual, "test1@test.com")
So(team1.OrgId, ShouldEqual, testOrgId)
So(team1.OrgId, ShouldEqual, testOrgID)
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)
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)
q1 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id}
q1 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id}
err = GetTeamMembers(q1)
So(err, ShouldBeNil)
So(q1.Result, ShouldHaveLength, 2)
So(q1.Result[0].TeamId, ShouldEqual, team1.Id)
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].Login, ShouldEqual, "loginuser1")
So(q1.Result[1].OrgId, ShouldEqual, testOrgId)
So(q1.Result[1].OrgId, ShouldEqual, testOrgID)
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)
So(err, ShouldBeNil)
So(q2.Result, ShouldHaveLength, 1)
So(q2.Result[0].TeamId, ShouldEqual, team1.Id)
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)
err = SearchTeams(query)
@@ -81,13 +78,13 @@ func TestTeamCommandsAndQueries(t *testing.T) {
team1 = query.Result.Teams[0]
So(team1.MemberCount, ShouldEqual, 2)
getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgId, Id: team1.Id}
getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgID, Id: team1.Id}
err = GetTeamById(getTeamQuery)
So(err, ShouldBeNil)
team1 = getTeamQuery.Result
So(team1.Name, ShouldEqual, "group1 name")
So(team1.Email, ShouldEqual, "test1@test.com")
So(team1.OrgId, ShouldEqual, testOrgId)
So(team1.OrgId, ShouldEqual, testOrgID)
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"})
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)
So(err, ShouldBeNil)
So(teamQuery.Page, ShouldEqual, 1)
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)
memberQuery := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true}
memberQuery := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id, External: true}
err = GetTeamMembers(memberQuery)
So(err, ShouldBeNil)
So(memberQuery.Result, ShouldHaveLength, 1)
So(memberQuery.Result[0].TeamId, ShouldEqual, team1.Id)
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].AuthModule, ShouldEqual, "oauth_github")
})
Convey("Should be able to update users in a team", func() {
userId := userIds[0]
team := group1.Result
addMemberCmd := models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team.Id, UserId: userId}
err = AddTeamMember(&addMemberCmd)
team := team1
err = sqlStore.AddTeamMember(userId, testOrgID, team.Id, false, 0)
So(err, ShouldBeNil)
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
err = GetTeamMembers(qBeforeUpdate)
So(err, ShouldBeNil)
So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0)
err = UpdateTeamMember(&models.UpdateTeamMemberCommand{
UserId: userId,
OrgId: testOrgId,
OrgId: testOrgID,
TeamId: team.Id,
Permission: models.PERMISSION_ADMIN,
})
So(err, ShouldBeNil)
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
err = GetTeamMembers(qAfterUpdate)
So(err, ShouldBeNil)
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() {
userID := userIds[0]
team := group1.Result
addMemberCmd := models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team.Id, UserId: userID}
err = AddTeamMember(&addMemberCmd)
team := team1
err = sqlStore.AddTeamMember(userID, testOrgID, team.Id, false, 0)
So(err, ShouldBeNil)
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
err = GetTeamMembers(qBeforeUpdate)
So(err, ShouldBeNil)
So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0)
@@ -159,14 +154,14 @@ func TestTeamCommandsAndQueries(t *testing.T) {
invalidPermissionLevel := models.PERMISSION_EDIT
err = UpdateTeamMember(&models.UpdateTeamMemberCommand{
UserId: userID,
OrgId: testOrgId,
OrgId: testOrgID,
TeamId: team.Id,
Permission: invalidPermissionLevel,
})
So(err, ShouldBeNil)
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team.Id}
err = GetTeamMembers(qAfterUpdate)
So(err, ShouldBeNil)
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() {
err = UpdateTeamMember(&models.UpdateTeamMemberCommand{
UserId: 1,
OrgId: testOrgId,
TeamId: group1.Result.Id,
OrgId: testOrgID,
TeamId: team1.Id,
Permission: models.PERMISSION_ADMIN,
})
@@ -184,24 +179,24 @@ func TestTeamCommandsAndQueries(t *testing.T) {
})
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)
So(err, ShouldBeNil)
So(len(query.Result.Teams), ShouldEqual, 2)
So(query.Result.TotalCount, ShouldEqual, 2)
query2 := &models.SearchTeamsQuery{OrgId: testOrgId, Query: ""}
query2 := &models.SearchTeamsQuery{OrgId: testOrgID, Query: ""}
err = SearchTeams(query2)
So(err, ShouldBeNil)
So(len(query2.Result.Teams), ShouldEqual, 2)
})
Convey("Should be able to return all teams a user is member of", func() {
groupId := group2.Result.Id
err := AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
groupId := team2.Id
err := sqlStore.AddTeamMember(userIds[0], testOrgID, groupId, false, 0)
So(err, ShouldBeNil)
query := &models.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]}
query := &models.GetTeamsByUserQuery{OrgId: testOrgID, UserId: userIds[0]}
err = GetTeamsByUser(query)
So(err, ShouldBeNil)
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() {
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)
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)
q2 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: group1.Result.Id}
q2 := &models.GetTeamMembersQuery{OrgId: testOrgID, TeamId: team1.Id}
err = GetTeamMembers(q2)
So(err, ShouldBeNil)
So(len(q2.Result), ShouldEqual, 0)
})
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)
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)
})
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)
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)
})
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)
})
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)
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)
})
})
Convey("Should be able to remove a group with users and permissions", func() {
groupId := group2.Result.Id
err := AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[1]})
groupId := team2.Id
err := sqlStore.AddTeamMember(userIds[1], testOrgID, groupId, false, 0)
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)
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)
err = DeleteTeam(&models.DeleteTeamCommand{OrgId: testOrgId, Id: groupId})
err = DeleteTeam(&models.DeleteTeamCommand{OrgId: testOrgID, Id: groupId})
So(err, ShouldBeNil)
query := &models.GetTeamByIdQuery{OrgId: testOrgId, Id: groupId}
query := &models.GetTeamByIdQuery{OrgId: testOrgID, Id: groupId}
err = GetTeamById(query)
So(err, ShouldEqual, models.ErrTeamNotFound)
permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: testOrgId}
permQuery := &models.GetDashboardAclInfoListQuery{DashboardID: 1, OrgID: testOrgID}
err = GetDashboardAclInfoList(permQuery)
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() {
groupId := group2.Result.Id
err := AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
groupId := team2.Id
err := sqlStore.AddTeamMember(userIds[0], testOrgID, groupId, false, 0)
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)
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)
So(err, ShouldBeNil)
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)
So(err, ShouldBeNil)
So(query.Result, ShouldBeTrue)
@@ -295,29 +292,29 @@ func TestTeamCommandsAndQueries(t *testing.T) {
signedInUser := &models.SignedInUser{Login: "loginuser0"}
hiddenUsers := map[string]struct{}{"loginuser0": {}, "loginuser1": {}}
teamId := group1.Result.Id
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: teamId, UserId: userIds[0]})
teamId := team1.Id
err = sqlStore.AddTeamMember(userIds[0], testOrgID, teamId, false, 0)
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)
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: teamId, UserId: userIds[2]})
err = sqlStore.AddTeamMember(userIds[2], testOrgID, teamId, false, 0)
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)
So(err, ShouldBeNil)
So(searchQuery.Result.Teams, ShouldHaveLength, 2)
team1 := searchQuery.Result.Teams[0]
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)
So(err, ShouldBeNil)
So(searchQueryFilteredByUser.Result.Teams, ShouldHaveLength, 1)
team1 = searchQuery.Result.Teams[0]
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)
So(err, ShouldBeNil)
So(getTeamQuery.Result.MemberCount, ShouldEqual, 2)

View File

@@ -31,12 +31,10 @@ func (ss *SQLStore) addUserQueryAndCommandHandlers() {
bus.AddHandler("sql", DisableUser)
bus.AddHandler("sql", BatchDisableUsers)
bus.AddHandler("sql", DeleteUser)
bus.AddHandler("sql", UpdateUserPermissions)
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 {
return -1, nil
}
@@ -191,8 +189,9 @@ func (ss *SQLStore) createUser(ctx context.Context, args userCreationArgs, skipO
return user, nil
}
func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
return inTransactionCtx(ctx, func(sess *DBSession) error {
func (ss *SQLStore) CreateUser(ctx context.Context, cmd models.CreateUserCommand) (*models.User, error) {
var user *models.User
err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error {
orgId, err := getOrgIdForNewUser(sess, cmd)
if err != nil {
return err
@@ -202,13 +201,16 @@ func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
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 {
return models.ErrUserAlreadyExists
}
// create user
user := models.User{
user = &models.User{
Email: cmd.Email,
Name: cmd.Name,
Login: cmd.Login,
@@ -243,7 +245,7 @@ func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
sess.UseBool("is_admin")
if _, err := sess.Insert(&user); err != nil {
if _, err := sess.Insert(user); err != nil {
return err
}
@@ -255,8 +257,6 @@ func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
Email: user.Email,
})
cmd.Result = user
// create org user link
if !cmd.SkipOrgSetup {
orgUser := models.OrgUser{
@@ -282,6 +282,8 @@ func CreateUser(ctx context.Context, cmd *models.CreateUserCommand) error {
return nil
})
return user, err
}
func GetUserById(query *models.GetUserByIdQuery) error {
@@ -729,14 +731,14 @@ func deleteUserInTransaction(sess *DBSession, cmd *models.DeleteUserCommand) err
return nil
}
func UpdateUserPermissions(cmd *models.UpdateUserPermissionsCommand) error {
return inTransaction(func(sess *DBSession) error {
user := models.User{}
if _, err := sess.ID(cmd.UserId).Get(&user); err != nil {
func (ss *SQLStore) UpdateUserPermissions(userID int64, isAdmin bool) error {
return ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
var user models.User
if _, err := sess.ID(userID).Get(&user); err != nil {
return err
}
user.IsAdmin = cmd.IsGrafanaAdmin
user.IsAdmin = isAdmin
sess.UseBool("is_admin")
_, err := sess.ID(user.Id).Update(&user)

View File

@@ -15,18 +15,16 @@ import (
//nolint:goconst
func TestUserAuth(t *testing.T) {
InitTestDB(t)
sqlStore := InitTestDB(t)
Convey("Given 5 users", t, func() {
var err error
var cmd *models.CreateUserCommand
for i := 0; i < 5; i++ {
cmd = &models.CreateUserCommand{
cmd := models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
}
err = CreateUser(context.Background(), cmd)
_, err := sqlStore.CreateUser(context.Background(), cmd)
So(err, ShouldBeNil)
}
@@ -46,7 +44,7 @@ func TestUserAuth(t *testing.T) {
login := "loginuser0"
query := &models.GetUserByAuthInfoQuery{Login: login}
err = GetUserByAuthInfo(query)
err := GetUserByAuthInfo(query)
So(err, ShouldBeNil)
So(query.Result.Login, ShouldEqual, login)
@@ -82,7 +80,7 @@ func TestUserAuth(t *testing.T) {
Convey("Can set & locate by AuthModule and AuthId", func() {
// get nonexistent user_auth entry
query := &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test"}
err = GetUserByAuthInfo(query)
err := GetUserByAuthInfo(query)
So(err, ShouldEqual, models.ErrUserNotFound)
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
query := &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test", AuthId: "test"}
err = GetUserByAuthInfo(query)
err := GetUserByAuthInfo(query)
So(err, ShouldBeNil)
So(query.Result.Login, ShouldEqual, login)
@@ -179,7 +177,7 @@ func TestUserAuth(t *testing.T) {
// Make the first log-in during the past
getTime = func() time.Time { return time.Now().AddDate(0, 0, -2) }
query := &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test1", AuthId: "test1"}
err = GetUserByAuthInfo(query)
err := GetUserByAuthInfo(query)
getTime = time.Now
So(err, ShouldBeNil)

View File

@@ -20,17 +20,17 @@ func TestUserDataAccess(t *testing.T) {
ss := InitTestDB(t)
Convey("Creates a user", func() {
cmd := &models.CreateUserCommand{
cmd := models.CreateUserCommand{
Email: "usertest@test.com",
Name: "user name",
Login: "user_test_login",
}
err := CreateUser(context.Background(), cmd)
user, err := ss.CreateUser(context.Background(), cmd)
So(err, ShouldBeNil)
Convey("Loading a user", func() {
query := models.GetUserByIdQuery{Id: cmd.Result.Id}
query := models.GetUserByIdQuery{Id: user.Id}
err := GetUserById(&query)
So(err, ShouldBeNil)
@@ -43,18 +43,18 @@ func TestUserDataAccess(t *testing.T) {
})
Convey("Creates disabled user", func() {
cmd := &models.CreateUserCommand{
cmd := models.CreateUserCommand{
Email: "usertest@test.com",
Name: "user name",
Login: "user_test_login",
IsDisabled: true,
}
err := CreateUser(context.Background(), cmd)
user, err := ss.CreateUser(context.Background(), cmd)
So(err, ShouldBeNil)
Convey("Loading a user", func() {
query := models.GetUserByIdQuery{Id: cmd.Result.Id}
query := models.GetUserByIdQuery{Id: user.Id}
err := GetUserById(&query)
So(err, ShouldBeNil)
@@ -78,18 +78,18 @@ func TestUserDataAccess(t *testing.T) {
So(err, ShouldBeNil)
Convey("Creates user assigned to other organization", func() {
cmd := &models.CreateUserCommand{
cmd := models.CreateUserCommand{
Email: "usertest@test.com",
Name: "user name",
Login: "user_test_login",
OrgId: orgCmd.Result.Id,
}
err := CreateUser(context.Background(), cmd)
user, err := ss.CreateUser(context.Background(), cmd)
So(err, ShouldBeNil)
Convey("Loading a user", func() {
query := models.GetUserByIdQuery{Id: cmd.Result.Id}
query := models.GetUserByIdQuery{Id: user.Id}
err := GetUserById(&query)
So(err, ShouldBeNil)
@@ -104,20 +104,20 @@ func TestUserDataAccess(t *testing.T) {
Convey("Don't create user assigned to unknown organization", func() {
const nonExistingOrgID = 10000
cmd := &models.CreateUserCommand{
cmd := models.CreateUserCommand{
Email: "usertest@test.com",
Name: "user name",
Login: "user_test_login",
OrgId: nonExistingOrgID,
}
err := CreateUser(context.Background(), cmd)
_, err := ss.CreateUser(context.Background(), cmd)
So(err, ShouldEqual, models.ErrOrgNotFound)
})
})
Convey("Given 5 users", func() {
users := createFiveTestUsers(func(i int) *models.CreateUserCommand {
users := createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
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() {
ss = InitTestDB(t)
createFiveTestUsers(func(i int) *models.CreateUserCommand {
createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
@@ -269,7 +269,7 @@ func TestUserDataAccess(t *testing.T) {
So(third, ShouldBeTrue)
ss = InitTestDB(t)
users = createFiveTestUsers(func(i int) *models.CreateUserCommand {
users = createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
@@ -286,7 +286,7 @@ func TestUserDataAccess(t *testing.T) {
})
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,
Permission: models.PERMISSION_EDIT,
})
@@ -365,7 +365,7 @@ func TestUserDataAccess(t *testing.T) {
Convey("Should enable all users", func() {
ss = InitTestDB(t)
createFiveTestUsers(func(i int) *models.CreateUserCommand {
createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
@@ -392,7 +392,7 @@ func TestUserDataAccess(t *testing.T) {
Convey("Should disable only specific users", func() {
ss = InitTestDB(t)
users = createFiveTestUsers(func(i int) *models.CreateUserCommand {
users = createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
@@ -438,7 +438,7 @@ func TestUserDataAccess(t *testing.T) {
// Since previous tests were destructive
ss = InitTestDB(t)
users = createFiveTestUsers(func(i int) *models.CreateUserCommand {
users = createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
@@ -546,25 +546,22 @@ func TestUserDataAccess(t *testing.T) {
})
Convey("Given one grafana admin user", func() {
var err error
createUserCmd := &models.CreateUserCommand{
createUserCmd := models.CreateUserCommand{
Email: fmt.Sprint("admin", "@test.com"),
Name: "admin",
Login: "admin",
IsAdmin: true,
}
err = CreateUser(context.Background(), createUserCmd)
user, err := ss.CreateUser(context.Background(), createUserCmd)
So(err, ShouldBeNil)
Convey("Cannot make themselves a non-admin", func() {
updateUserPermsCmd := models.UpdateUserPermissionsCommand{IsGrafanaAdmin: false, UserId: 1}
updatePermsError := UpdateUserPermissions(&updateUserPermsCmd)
updatePermsError := ss.UpdateUserPermissions(1, false)
So(updatePermsError, ShouldEqual, models.ErrLastGrafanaAdmin)
query := models.GetUserByIdQuery{Id: createUserCmd.Result.Id}
query := models.GetUserByIdQuery{Id: user.Id}
getUserError := GetUserById(&query)
So(getUserError, ShouldBeNil)
So(query.Result.IsAdmin, ShouldEqual, true)
@@ -574,33 +571,33 @@ func TestUserDataAccess(t *testing.T) {
Convey("Given one user", func() {
const email = "user@test.com"
const username = "user"
createUserCmd := &models.CreateUserCommand{
createUserCmd := models.CreateUserCommand{
Email: email,
Name: "user",
Login: username,
}
err := CreateUser(context.Background(), createUserCmd)
_, err := ss.CreateUser(context.Background(), createUserCmd)
So(err, ShouldBeNil)
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,
Name: "user2",
Login: "user2",
SkipOrgSetup: true,
}
err := CreateUser(context.Background(), createUserCmd)
_, err := ss.CreateUser(context.Background(), createUserCmd)
So(err, ShouldEqual, models.ErrUserAlreadyExists)
})
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",
Name: "user2",
Login: username,
SkipOrgSetup: true,
}
err := CreateUser(context.Background(), createUserCmd)
_, err := ss.CreateUser(context.Background(), createUserCmd)
So(err, ShouldEqual, models.ErrUserAlreadyExists)
})
})
@@ -618,15 +615,15 @@ func GetOrgUsersForTest(query *models.GetOrgUsersQuery) error {
return err
}
func createFiveTestUsers(fn func(i int) *models.CreateUserCommand) []models.User {
var err error
var cmd *models.CreateUserCommand
func createFiveTestUsers(t *testing.T, sqlStore *SQLStore, fn func(i int) *models.CreateUserCommand) []models.User {
t.Helper()
users := []models.User{}
for i := 0; i < 5; i++ {
cmd = fn(i)
cmd := fn(i)
err = CreateUser(context.Background(), cmd)
users = append(users, cmd.Result)
user, err := sqlStore.CreateUser(context.Background(), *cmd)
users = append(users, *user)
So(err, ShouldBeNil)
}

View File

@@ -18,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/opentracing/opentracing-go"
@@ -27,8 +26,9 @@ import (
// ApplicationInsightsDatasource calls the application insights query API.
type ApplicationInsightsDatasource struct {
httpClient *http.Client
dsInfo *models.DataSource
httpClient *http.Client
dsInfo *models.DataSource
pluginManager plugins.Manager
}
// 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) {
// find plugin
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if plugin == nil {
return nil, errors.New("unable to find datasource plugin Azure Application Insights")
}

View File

@@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/opentracing/opentracing-go"
@@ -26,8 +25,9 @@ import (
// AzureLogAnalyticsDatasource calls the Azure Log Analytics API's
type AzureLogAnalyticsDatasource struct {
httpClient *http.Client
dsInfo *models.DataSource
httpClient *http.Client
dsInfo *models.DataSource
pluginManager plugins.Manager
}
// 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))
// find plugin
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if plugin == nil {
return nil, errors.New("unable to find datasource plugin Azure Monitor")
}
cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor")

View File

@@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/api/pluginproxy"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
opentracing "github.com/opentracing/opentracing-go"
@@ -26,8 +25,9 @@ import (
// AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported
type AzureMonitorDatasource struct {
httpClient *http.Client
dsInfo *models.DataSource
httpClient *http.Client
dsInfo *models.DataSource
pluginManager plugins.Manager
}
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) {
// find plugin
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if plugin == nil {
return nil, errors.New("unable to find datasource plugin Azure Monitor")
}

View File

@@ -26,6 +26,7 @@ func init() {
}
type Service struct {
PluginManager plugins.Manager `inject:""`
}
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
type AzureMonitorExecutor struct {
httpClient *http.Client
dsInfo *models.DataSource
httpClient *http.Client
dsInfo *models.DataSource
pluginManager plugins.Manager
}
// NewAzureMonitorExecutor initializes a http client
@@ -46,8 +48,9 @@ func (s *Service) NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, er
}
return &AzureMonitorExecutor{
httpClient: httpClient,
dsInfo: dsInfo,
httpClient: httpClient,
dsInfo: dsInfo,
pluginManager: s.PluginManager,
}, nil
}
@@ -82,23 +85,27 @@ func (e *AzureMonitorExecutor) DataQuery(ctx context.Context, dsInfo *models.Dat
}
azDatasource := &AzureMonitorDatasource{
httpClient: e.httpClient,
dsInfo: e.dsInfo,
httpClient: e.httpClient,
dsInfo: e.dsInfo,
pluginManager: e.pluginManager,
}
aiDatasource := &ApplicationInsightsDatasource{
httpClient: e.httpClient,
dsInfo: e.dsInfo,
httpClient: e.httpClient,
dsInfo: e.dsInfo,
pluginManager: e.pluginManager,
}
alaDatasource := &AzureLogAnalyticsDatasource{
httpClient: e.httpClient,
dsInfo: e.dsInfo,
httpClient: e.httpClient,
dsInfo: e.dsInfo,
pluginManager: e.pluginManager,
}
iaDatasource := &InsightsAnalyticsDatasource{
httpClient: e.httpClient,
dsInfo: e.dsInfo,
httpClient: e.httpClient,
dsInfo: e.dsInfo,
pluginManager: e.pluginManager,
}
azResult, err := azDatasource.executeTimeSeriesQuery(ctx, azureMonitorQueries, *tsdbQuery.TimeRange)

View File

@@ -15,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/api/pluginproxy"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/opentracing/opentracing-go"
@@ -23,8 +22,9 @@ import (
)
type InsightsAnalyticsDatasource struct {
httpClient *http.Client
dsInfo *models.DataSource
httpClient *http.Client
dsInfo *models.DataSource
pluginManager plugins.Manager
}
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) {
// find plugin
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if plugin == nil {
return nil, errors.New("unable to find datasource plugin Azure Application Insights")
}

View File

@@ -17,7 +17,6 @@ import (
"time"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana-plugin-sdk-go/data"
@@ -73,6 +72,7 @@ func init() {
}
type Service struct {
PluginManager plugins.Manager `inject:""`
}
func (s *Service) Init() error {
@@ -81,8 +81,9 @@ func (s *Service) Init() error {
// Executor executes queries for the CloudMonitoring datasource.
type Executor struct {
httpClient *http.Client
dsInfo *models.DataSource
httpClient *http.Client
dsInfo *models.DataSource
pluginManager plugins.Manager
}
// NewExecutor returns an Executor.
@@ -93,8 +94,9 @@ func (s *Service) NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, er
}
return &Executor{
httpClient: httpClient,
dsInfo: dsInfo,
httpClient: httpClient,
dsInfo: dsInfo,
pluginManager: s.PluginManager,
}, 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))
// find plugin
plugin, ok := manager.DataSources[dsInfo.Type]
if !ok {
plugin := e.pluginManager.GetDataSource(dsInfo.Type)
if plugin == nil {
return nil, errors.New("unable to find datasource plugin CloudMonitoring")
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/manager"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor"
@@ -46,7 +45,7 @@ type Service struct {
PostgresService *postgres.PostgresService `inject:""`
CloudMonitoringService *cloudmonitoring.Service `inject:""`
AzureMonitorService *azuremonitor.Service `inject:""`
PluginManager *manager.PluginManager `inject:""`
PluginManager plugins.Manager `inject:""`
registry map[string]func(*models.DataSource) (plugins.DataPlugin, error)
}