mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into 10630_folder_api
This commit is contained in:
@@ -52,6 +52,7 @@ func GetAlerts(c *middleware.Context) Response {
|
||||
DashboardId: c.QueryInt64("dashboardId"),
|
||||
PanelId: c.QueryInt64("panelId"),
|
||||
Limit: c.QueryInt64("limit"),
|
||||
User: c.SignedInUser,
|
||||
}
|
||||
|
||||
states := c.QueryStrings("state")
|
||||
@@ -63,74 +64,11 @@ func GetAlerts(c *middleware.Context) Response {
|
||||
return ApiError(500, "List alerts failed", err)
|
||||
}
|
||||
|
||||
alertDTOs, resp := transformToDTOs(query.Result, c)
|
||||
if resp != nil {
|
||||
return resp
|
||||
for _, alert := range query.Result {
|
||||
alert.Url = models.GetDashboardUrl(alert.DashboardUid, alert.DashboardSlug)
|
||||
}
|
||||
|
||||
return Json(200, alertDTOs)
|
||||
}
|
||||
|
||||
func transformToDTOs(alerts []*models.Alert, c *middleware.Context) ([]*dtos.AlertRule, Response) {
|
||||
if len(alerts) == 0 {
|
||||
return []*dtos.AlertRule{}, nil
|
||||
}
|
||||
|
||||
dashboardIds := make([]int64, 0)
|
||||
alertDTOs := make([]*dtos.AlertRule, 0)
|
||||
for _, alert := range alerts {
|
||||
dashboardIds = append(dashboardIds, alert.DashboardId)
|
||||
alertDTOs = append(alertDTOs, &dtos.AlertRule{
|
||||
Id: alert.Id,
|
||||
DashboardId: alert.DashboardId,
|
||||
PanelId: alert.PanelId,
|
||||
Name: alert.Name,
|
||||
Message: alert.Message,
|
||||
State: alert.State,
|
||||
NewStateDate: alert.NewStateDate,
|
||||
ExecutionError: alert.ExecutionError,
|
||||
EvalData: alert.EvalData,
|
||||
})
|
||||
}
|
||||
|
||||
dashboardsQuery := models.GetDashboardsQuery{
|
||||
DashboardIds: dashboardIds,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&dashboardsQuery); err != nil {
|
||||
return nil, ApiError(500, "List alerts failed", err)
|
||||
}
|
||||
|
||||
//TODO: should be possible to speed this up with lookup table
|
||||
for _, alert := range alertDTOs {
|
||||
for _, dash := range dashboardsQuery.Result {
|
||||
if alert.DashboardId == dash.Id {
|
||||
alert.Url = dash.GenerateUrl()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
permissionsQuery := models.GetDashboardPermissionsForUserQuery{
|
||||
DashboardIds: dashboardIds,
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.SignedInUser.UserId,
|
||||
OrgRole: c.SignedInUser.OrgRole,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&permissionsQuery); err != nil {
|
||||
return nil, ApiError(500, "List alerts failed", err)
|
||||
}
|
||||
|
||||
for _, alert := range alertDTOs {
|
||||
for _, perm := range permissionsQuery.Result {
|
||||
if alert.DashboardId == perm.DashboardId {
|
||||
alert.CanEdit = perm.Permission > 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return alertDTOs, nil
|
||||
return Json(200, query.Result)
|
||||
}
|
||||
|
||||
// POST /api/alerts/test
|
||||
@@ -288,7 +226,7 @@ func PauseAlert(c *middleware.Context, dto dtos.PauseAlertCommand) Response {
|
||||
return ApiError(500, "Get Alert failed", err)
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(query.Result.DashboardId, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(query.Result.DashboardId, c.OrgId, c.SignedInUser)
|
||||
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
|
||||
if err != nil {
|
||||
return ApiError(500, "Error while checking permissions for Alert", err)
|
||||
|
||||
@@ -278,7 +278,7 @@ func canSaveByDashboardId(c *middleware.Context, dashboardId int64) (bool, error
|
||||
}
|
||||
|
||||
if dashboardId > 0 {
|
||||
guardian := guardian.NewDashboardGuardian(dashboardId, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashboardId, c.OrgId, c.SignedInUser)
|
||||
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -270,8 +270,6 @@ func (hs *HttpServer) registerRoutes() {
|
||||
dashboardRoute.Get("/tags", GetDashboardTags)
|
||||
dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
|
||||
|
||||
dashboardRoute.Get("/folders", wrap(GetFoldersForSignedInUser))
|
||||
|
||||
dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute RouteRegister) {
|
||||
dashIdRoute.Get("/versions", wrap(GetDashboardVersions))
|
||||
dashIdRoute.Get("/versions/:id", wrap(GetDashboardVersion))
|
||||
@@ -280,7 +278,6 @@ func (hs *HttpServer) registerRoutes() {
|
||||
dashIdRoute.Group("/acl", func(aclRoute RouteRegister) {
|
||||
aclRoute.Get("/", wrap(GetDashboardAclList))
|
||||
aclRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), wrap(UpdateDashboardAcl))
|
||||
aclRoute.Delete("/:aclId", wrap(DeleteDashboardAcl))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
|
||||
@@ -50,7 +49,7 @@ func GetDashboard(c *middleware.Context) Response {
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dash.Id, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dash.Id, c.OrgId, c.SignedInUser)
|
||||
if canView, err := guardian.CanView(); err != nil || !canView {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
@@ -158,7 +157,7 @@ func DeleteDashboard(c *middleware.Context) Response {
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dash.Id, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dash.Id, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
@@ -178,7 +177,7 @@ func DeleteDashboardByUid(c *middleware.Context) Response {
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dash.Id, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dash.Id, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
@@ -198,32 +197,7 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
|
||||
|
||||
dash := cmd.GetDashboardModel()
|
||||
|
||||
dashId := dash.Id
|
||||
|
||||
// if new dashboard, use parent folder permissions instead
|
||||
if dashId == 0 {
|
||||
dashId = cmd.FolderId
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
if dash.IsFolder && dash.FolderId > 0 {
|
||||
return ApiError(400, m.ErrDashboardFolderCannotHaveParent.Error(), nil)
|
||||
}
|
||||
|
||||
// Check if Title is empty
|
||||
if dash.Title == "" {
|
||||
return ApiError(400, m.ErrDashboardTitleEmpty.Error(), nil)
|
||||
}
|
||||
|
||||
if dash.IsFolder && strings.ToLower(dash.Title) == strings.ToLower(m.RootFolderName) {
|
||||
return ApiError(400, "A folder already exists with that name", nil)
|
||||
}
|
||||
|
||||
if dash.Id == 0 {
|
||||
if dash.Id == 0 && dash.Uid == "" {
|
||||
limitReached, err := middleware.QuotaReached(c, "dashboard")
|
||||
if err != nil {
|
||||
return ApiError(500, "failed to get quota", err)
|
||||
@@ -237,27 +211,34 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
|
||||
Dashboard: dash,
|
||||
Message: cmd.Message,
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
User: c.SignedInUser,
|
||||
Overwrite: cmd.Overwrite,
|
||||
}
|
||||
|
||||
dashboard, err := dashboards.GetRepository().SaveDashboard(dashItem)
|
||||
dashboard, err := dashboards.NewService().SaveDashboard(dashItem)
|
||||
|
||||
if err == m.ErrDashboardTitleEmpty ||
|
||||
err == m.ErrDashboardWithSameNameAsFolder ||
|
||||
err == m.ErrDashboardFolderWithSameNameAsDashboard ||
|
||||
err == m.ErrDashboardTypeMismatch {
|
||||
err == m.ErrDashboardTypeMismatch ||
|
||||
err == m.ErrDashboardInvalidUid ||
|
||||
err == m.ErrDashboardUidToLong ||
|
||||
err == m.ErrDashboardWithSameUIDExists ||
|
||||
err == m.ErrFolderNotFound ||
|
||||
err == m.ErrDashboardFolderCannotHaveParent ||
|
||||
err == m.ErrDashboardFolderNameExists {
|
||||
return ApiError(400, err.Error(), nil)
|
||||
}
|
||||
|
||||
if err == m.ErrDashboardUpdateAccessDenied {
|
||||
return ApiError(403, err.Error(), err)
|
||||
}
|
||||
|
||||
if err == m.ErrDashboardContainsInvalidAlertData {
|
||||
return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == m.ErrDashboardWithSameUIDExists {
|
||||
return Json(412, util.DynMap{"status": "name-exists", "message": err.Error()})
|
||||
}
|
||||
if err == m.ErrDashboardWithSameNameInFolderExists {
|
||||
return Json(412, util.DynMap{"status": "name-exists", "message": err.Error()})
|
||||
}
|
||||
@@ -282,8 +263,6 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
|
||||
return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
|
||||
}
|
||||
|
||||
dashboard.IsFolder = dash.IsFolder
|
||||
|
||||
c.TimeRequest(metrics.M_Api_Dashboard_Save)
|
||||
return Json(200, util.DynMap{
|
||||
"status": "success",
|
||||
@@ -358,7 +337,7 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
|
||||
func GetDashboardVersions(c *middleware.Context) Response {
|
||||
dashId := c.ParamsInt64(":dashboardId")
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
@@ -397,7 +376,7 @@ func GetDashboardVersions(c *middleware.Context) Response {
|
||||
func GetDashboardVersion(c *middleware.Context) Response {
|
||||
dashId := c.ParamsInt64(":dashboardId")
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
@@ -465,7 +444,7 @@ func RestoreDashboardVersion(c *middleware.Context, apiCmd dtos.RestoreDashboard
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dash.Id, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dash.Id, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func GetDashboardAclList(c *middleware.Context) Response {
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
|
||||
|
||||
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
|
||||
return dashboardGuardianResponse(err)
|
||||
@@ -46,7 +46,7 @@ func UpdateDashboardAcl(c *middleware.Context, apiCmd dtos.UpdateDashboardAclCom
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
|
||||
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
@@ -84,33 +84,3 @@ func UpdateDashboardAcl(c *middleware.Context, apiCmd dtos.UpdateDashboardAclCom
|
||||
|
||||
return ApiSuccess("Dashboard acl updated")
|
||||
}
|
||||
|
||||
func DeleteDashboardAcl(c *middleware.Context) Response {
|
||||
dashId := c.ParamsInt64(":dashboardId")
|
||||
aclId := c.ParamsInt64(":aclId")
|
||||
|
||||
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
|
||||
if rsp != nil {
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
|
||||
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
if okToDelete, err := guardian.CheckPermissionBeforeRemove(m.PERMISSION_ADMIN, aclId); err != nil || !okToDelete {
|
||||
if err != nil {
|
||||
return ApiError(500, "Error while checking dashboard permissions", err)
|
||||
}
|
||||
|
||||
return ApiError(403, "Cannot remove own admin permission for a folder", nil)
|
||||
}
|
||||
|
||||
cmd := m.RemoveDashboardAclCommand{OrgId: c.OrgId, AclId: aclId}
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
return ApiError(500, "Failed to delete permission for user", err)
|
||||
}
|
||||
|
||||
return Json(200, "")
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ import (
|
||||
func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
Convey("Given a dashboard acl", t, func() {
|
||||
mockResult := []*m.DashboardAclInfoDTO{
|
||||
{Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permission: m.PERMISSION_VIEW},
|
||||
{Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permission: m.PERMISSION_EDIT},
|
||||
{Id: 3, OrgId: 1, DashboardId: 1, UserId: 4, Permission: m.PERMISSION_ADMIN},
|
||||
{Id: 4, OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW},
|
||||
{Id: 5, OrgId: 1, DashboardId: 1, TeamId: 2, Permission: m.PERMISSION_ADMIN},
|
||||
{OrgId: 1, DashboardId: 1, UserId: 2, Permission: m.PERMISSION_VIEW},
|
||||
{OrgId: 1, DashboardId: 1, UserId: 3, Permission: m.PERMISSION_EDIT},
|
||||
{OrgId: 1, DashboardId: 1, UserId: 4, Permission: m.PERMISSION_ADMIN},
|
||||
{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW},
|
||||
{OrgId: 1, DashboardId: 1, TeamId: 2, Permission: m.PERMISSION_ADMIN},
|
||||
}
|
||||
dtoRes := transformDashboardAclsToDTOs(mockResult)
|
||||
|
||||
@@ -92,21 +92,11 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
So(sc.resp.Code, ShouldEqual, 404)
|
||||
})
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/2/acl/6", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||
getDashboardNotFoundError = m.ErrDashboardNotFound
|
||||
sc.handlerFunc = DeleteDashboardAcl
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
|
||||
Convey("Should not be able to delete non-existing dashboard", func() {
|
||||
So(sc.resp.Code, ShouldEqual, 404)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is org editor and has admin permission in the ACL", func() {
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
|
||||
|
||||
Convey("Should be able to access ACL", func() {
|
||||
sc.handlerFunc = GetDashboardAclList
|
||||
@@ -116,36 +106,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
|
||||
|
||||
bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
Convey("Should be able to delete permission", func() {
|
||||
sc.handlerFunc = DeleteDashboardAcl
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/6", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
|
||||
|
||||
bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
Convey("Should not be able to delete their own Admin permission", func() {
|
||||
sc.handlerFunc = DeleteDashboardAcl
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should not be able to downgrade their own Admin permission", func() {
|
||||
cmd := dtos.UpdateDashboardAclCommand{
|
||||
Items: []dtos.DashboardAclUpdateItem{
|
||||
@@ -154,7 +114,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
|
||||
|
||||
CallPostAcl(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
@@ -170,34 +130,18 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 6, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
|
||||
|
||||
CallPostAcl(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is a member of a team in the ACL with admin permission", func() {
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardsId/acl/:aclId", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
teamResp = append(teamResp, &m.Team{Id: 2, OrgId: 1, Name: "UG2"})
|
||||
|
||||
bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
Convey("Should be able to delete permission", func() {
|
||||
sc.handlerFunc = DeleteDashboardAcl
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is org viewer and has edit permission in the ACL", func() {
|
||||
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_VIEWER, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT})
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT})
|
||||
|
||||
// Getting the permissions is an Admin permission
|
||||
Convey("Should not be able to get list of permissions from ACL", func() {
|
||||
@@ -207,21 +151,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_VIEWER, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT})
|
||||
|
||||
bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
Convey("Should be not be able to delete permission", func() {
|
||||
sc.handlerFunc = DeleteDashboardAcl
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is org editor and not in the ACL", func() {
|
||||
@@ -234,20 +163,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/user/1", "/api/dashboards/id/:dashboardsId/acl/user/:userId", m.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_VIEW})
|
||||
bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
Convey("Should be not be able to delete permission", func() {
|
||||
sc.handlerFunc = DeleteDashboardAcl
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -257,7 +172,6 @@ func transformDashboardAclsToDTOs(acls []*m.DashboardAclInfoDTO) []*m.DashboardA
|
||||
|
||||
for _, acl := range acls {
|
||||
dto := &m.DashboardAclInfoDTO{
|
||||
Id: acl.Id,
|
||||
OrgId: acl.OrgId,
|
||||
DashboardId: acl.DashboardId,
|
||||
Permission: acl.Permission,
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
@@ -9,38 +10,17 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type fakeDashboardRepo struct {
|
||||
inserted []*dashboards.SaveDashboardDTO
|
||||
provisioned []*m.DashboardProvisioning
|
||||
getDashboard []*m.Dashboard
|
||||
}
|
||||
|
||||
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardDTO) (*m.Dashboard, error) {
|
||||
repo.inserted = append(repo.inserted, json)
|
||||
return json.Dashboard, nil
|
||||
}
|
||||
|
||||
func (repo *fakeDashboardRepo) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *m.DashboardProvisioning) (*m.Dashboard, error) {
|
||||
repo.inserted = append(repo.inserted, dto)
|
||||
return dto.Dashboard, nil
|
||||
}
|
||||
|
||||
func (repo *fakeDashboardRepo) GetProvisionedDashboardData(name string) ([]*m.DashboardProvisioning, error) {
|
||||
return repo.provisioned, nil
|
||||
}
|
||||
|
||||
var fakeRepo *fakeDashboardRepo
|
||||
|
||||
// This tests two main scenarios. If a user has access to execute an action on a dashboard:
|
||||
// 1. and the dashboard is in a folder which does not have an acl
|
||||
// 2. and the dashboard is in a folder which does have an acl
|
||||
// This tests three main scenarios.
|
||||
// If a user has access to execute an action on a dashboard:
|
||||
// 1. and the dashboard is in a folder which does not have an acl
|
||||
// 2. and the dashboard is in a folder which does have an acl
|
||||
// 3. Post dashboard response tests
|
||||
|
||||
func TestDashboardApiEndpoint(t *testing.T) {
|
||||
Convey("Given a dashboard with a parent folder which does not have an acl", t, func() {
|
||||
@@ -81,14 +61,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"folderId": fakeDash.FolderId,
|
||||
"title": fakeDash.Title,
|
||||
"id": fakeDash.Id,
|
||||
}),
|
||||
}
|
||||
|
||||
// This tests two scenarios:
|
||||
// 1. user is an org viewer
|
||||
// 2. user is an org editor
|
||||
@@ -151,11 +123,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
CallGetDashboardVersions(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
|
||||
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
|
||||
CallPostDashboard(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is an Org Editor", func() {
|
||||
@@ -216,32 +183,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
CallGetDashboardVersions(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
|
||||
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
|
||||
CallPostDashboardShouldReturnSuccess(sc)
|
||||
})
|
||||
|
||||
Convey("When saving a dashboard folder in another folder", func() {
|
||||
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
|
||||
query.Result = fakeDash
|
||||
query.Result.IsFolder = true
|
||||
return nil
|
||||
})
|
||||
invalidCmd := m.SaveDashboardCommand{
|
||||
FolderId: fakeDash.FolderId,
|
||||
IsFolder: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"folderId": fakeDash.FolderId,
|
||||
"title": fakeDash.Title,
|
||||
}),
|
||||
}
|
||||
Convey("Should return an error", func() {
|
||||
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, invalidCmd, func(sc *scenarioContext) {
|
||||
CallPostDashboard(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 400)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -284,15 +225,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
FolderId: fakeDash.FolderId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": fakeDash.Id,
|
||||
"folderId": fakeDash.FolderId,
|
||||
"title": fakeDash.Title,
|
||||
}),
|
||||
}
|
||||
|
||||
// This tests six scenarios:
|
||||
// 1. user is an org viewer AND has no permissions for this dashboard
|
||||
// 2. user is an org editor AND has no permissions for this dashboard
|
||||
@@ -357,11 +289,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
CallGetDashboardVersions(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
|
||||
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
|
||||
CallPostDashboard(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is an Org Editor and has no permissions for this dashboard", func() {
|
||||
@@ -420,18 +347,13 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
CallGetDashboardVersions(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
|
||||
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
|
||||
CallPostDashboard(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is an Org Viewer but has an edit permission", func() {
|
||||
role := m.ROLE_VIEWER
|
||||
|
||||
mockResult := []*m.DashboardAclInfoDTO{
|
||||
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT},
|
||||
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT},
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
|
||||
@@ -494,10 +416,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
CallGetDashboardVersions(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
|
||||
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
|
||||
CallPostDashboardShouldReturnSuccess(sc)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is an Org Viewer and viewers can edit", func() {
|
||||
@@ -505,7 +423,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
setting.ViewersCanEdit = true
|
||||
|
||||
mockResult := []*m.DashboardAclInfoDTO{
|
||||
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
|
||||
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
|
||||
@@ -564,7 +482,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
role := m.ROLE_VIEWER
|
||||
|
||||
mockResult := []*m.DashboardAclInfoDTO{
|
||||
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN},
|
||||
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN},
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
|
||||
@@ -627,17 +545,13 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
CallGetDashboardVersions(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
|
||||
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
|
||||
CallPostDashboardShouldReturnSuccess(sc)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user is an Org Editor but has a view permission", func() {
|
||||
role := m.ROLE_EDITOR
|
||||
|
||||
mockResult := []*m.DashboardAclInfoDTO{
|
||||
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
|
||||
{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
|
||||
@@ -698,11 +612,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
CallGetDashboardVersions(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
|
||||
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
|
||||
CallPostDashboard(sc)
|
||||
So(sc.resp.Code, ShouldEqual, 403)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -736,6 +645,104 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Post dashboard response tests", t, func() {
|
||||
|
||||
// This tests that a valid request returns correct response
|
||||
|
||||
Convey("Given a correct request for creating a dashboard", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
UserId: 5,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
Overwrite: true,
|
||||
FolderId: 3,
|
||||
IsFolder: false,
|
||||
Message: "msg",
|
||||
}
|
||||
|
||||
mock := &dashboards.FakeDashboardService{
|
||||
SaveDashboardResult: &m.Dashboard{
|
||||
Id: 2,
|
||||
Uid: "uid",
|
||||
Title: "Dash",
|
||||
Slug: "dash",
|
||||
Version: 2,
|
||||
},
|
||||
}
|
||||
|
||||
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", mock, cmd, func(sc *scenarioContext) {
|
||||
CallPostDashboardShouldReturnSuccess(sc)
|
||||
|
||||
Convey("It should call dashboard service with correct data", func() {
|
||||
dto := mock.SavedDashboards[0]
|
||||
So(dto.OrgId, ShouldEqual, cmd.OrgId)
|
||||
So(dto.User.UserId, ShouldEqual, cmd.UserId)
|
||||
So(dto.Dashboard.FolderId, ShouldEqual, 3)
|
||||
So(dto.Dashboard.Title, ShouldEqual, "Dash")
|
||||
So(dto.Overwrite, ShouldBeTrue)
|
||||
So(dto.Message, ShouldEqual, "msg")
|
||||
})
|
||||
|
||||
Convey("It should return correct response data", func() {
|
||||
result := sc.ToJson()
|
||||
So(result.Get("status").MustString(), ShouldEqual, "success")
|
||||
So(result.Get("id").MustInt64(), ShouldEqual, 2)
|
||||
So(result.Get("uid").MustString(), ShouldEqual, "uid")
|
||||
So(result.Get("slug").MustString(), ShouldEqual, "dash")
|
||||
So(result.Get("url").MustString(), ShouldEqual, "/d/uid/dash")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// This tests that invalid requests returns expected error responses
|
||||
|
||||
Convey("Given incorrect requests for creating a dashboard", func() {
|
||||
testCases := []struct {
|
||||
SaveError error
|
||||
ExpectedStatusCode int
|
||||
}{
|
||||
{SaveError: m.ErrDashboardNotFound, ExpectedStatusCode: 404},
|
||||
{SaveError: m.ErrFolderNotFound, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412},
|
||||
{SaveError: m.ErrDashboardVersionMismatch, ExpectedStatusCode: 412},
|
||||
{SaveError: m.ErrDashboardTitleEmpty, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardContainsInvalidAlertData, ExpectedStatusCode: 500},
|
||||
{SaveError: m.ErrDashboardFailedToUpdateAlertData, ExpectedStatusCode: 500},
|
||||
{SaveError: m.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500},
|
||||
{SaveError: m.ErrDashboardTypeMismatch, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardFolderNameExists, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403},
|
||||
{SaveError: m.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardUidToLong, ExpectedStatusCode: 400},
|
||||
{SaveError: m.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412},
|
||||
}
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "",
|
||||
}),
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
mock := &dashboards.FakeDashboardService{
|
||||
SaveDashboardError: tc.SaveError,
|
||||
}
|
||||
|
||||
postDashboardScenario(fmt.Sprintf("Expect '%s' error when calling POST on", tc.SaveError.Error()), "/api/dashboards", "/api/dashboards", mock, cmd, func(sc *scenarioContext) {
|
||||
CallPostDashboard(sc)
|
||||
So(sc.resp.Code, ShouldEqual, tc.ExpectedStatusCode)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
|
||||
@@ -794,19 +801,6 @@ func CallDeleteDashboardByUid(sc *scenarioContext) {
|
||||
}
|
||||
|
||||
func CallPostDashboard(sc *scenarioContext) {
|
||||
bus.AddHandler("test", func(cmd *alerting.ValidateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *m.SaveDashboardCommand) error {
|
||||
cmd.Result = &m.Dashboard{Id: 2, Slug: "Dash", Version: 2}
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *alerting.UpdateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
@@ -814,33 +808,29 @@ func CallPostDashboardShouldReturnSuccess(sc *scenarioContext) {
|
||||
CallPostDashboard(sc)
|
||||
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
result := sc.ToJson()
|
||||
So(result.Get("status").MustString(), ShouldEqual, "success")
|
||||
So(result.Get("id").MustInt64(), ShouldBeGreaterThan, 0)
|
||||
So(result.Get("uid").MustString(), ShouldNotBeNil)
|
||||
So(result.Get("slug").MustString(), ShouldNotBeNil)
|
||||
So(result.Get("url").MustString(), ShouldNotBeNil)
|
||||
}
|
||||
|
||||
func postDashboardScenario(desc string, url string, routePattern string, role m.RoleType, cmd m.SaveDashboardCommand, fn scenarioFunc) {
|
||||
func postDashboardScenario(desc string, url string, routePattern string, mock *dashboards.FakeDashboardService, cmd m.SaveDashboardCommand, fn scenarioFunc) {
|
||||
Convey(desc+" "+url, func() {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(url)
|
||||
sc.defaultHandler = wrap(func(c *middleware.Context) Response {
|
||||
sc.context = c
|
||||
sc.context.UserId = TestUserID
|
||||
sc.context.OrgId = TestOrgID
|
||||
sc.context.OrgRole = role
|
||||
sc.context.SignedInUser = &m.SignedInUser{OrgId: cmd.OrgId, UserId: cmd.UserId}
|
||||
|
||||
return PostDashboard(c, cmd)
|
||||
})
|
||||
|
||||
fakeRepo = &fakeDashboardRepo{}
|
||||
dashboards.SetRepository(fakeRepo)
|
||||
origNewDashboardService := dashboards.NewService
|
||||
dashboards.MockDashboardService(mock)
|
||||
|
||||
sc.m.Post(routePattern, sc.defaultHandler)
|
||||
|
||||
defer func() {
|
||||
dashboards.NewService = origNewDashboardService
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -37,35 +37,19 @@ func folderGuardianResponse(err error) Response {
|
||||
return ApiError(403, "Access denied to this folder", nil)
|
||||
}
|
||||
|
||||
func GetFoldersForSignedInUser(c *middleware.Context) Response {
|
||||
title := c.Query("query")
|
||||
query := m.GetFoldersForSignedInUserQuery{
|
||||
OrgId: c.OrgId,
|
||||
SignedInUser: c.SignedInUser,
|
||||
Title: title,
|
||||
}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to retrieve folders", err)
|
||||
}
|
||||
|
||||
return Json(200, query.Result)
|
||||
}
|
||||
|
||||
func GetFolder(c *middleware.Context) Response {
|
||||
folder, rsp := getFolderHelper(c.OrgId, c.ParamsInt64(":id"), c.Params(":uid"))
|
||||
if rsp != nil {
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(folder.Id, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
||||
if canView, err := guardian.CanView(); err != nil || !canView {
|
||||
fmt.Printf("%v", err)
|
||||
return folderGuardianResponse(err)
|
||||
}
|
||||
|
||||
return Json(200, toDto(guardian, folder))
|
||||
return Json(200, toFolderDto(&guardian, folder))
|
||||
}
|
||||
|
||||
func CreateFolder(c *middleware.Context, cmd m.CreateFolderCommand) Response {
|
||||
@@ -74,7 +58,7 @@ func CreateFolder(c *middleware.Context, cmd m.CreateFolderCommand) Response {
|
||||
|
||||
dashFolder := cmd.GetDashboardModel()
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(0, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(0, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return folderGuardianResponse(err)
|
||||
}
|
||||
@@ -103,7 +87,7 @@ func CreateFolder(c *middleware.Context, cmd m.CreateFolderCommand) Response {
|
||||
return toFolderError(err)
|
||||
}
|
||||
|
||||
return Json(200, toDto(guardian, folder))
|
||||
return Json(200, toFolderDto(&guardian, folder))
|
||||
}
|
||||
|
||||
func UpdateFolder(c *middleware.Context, cmd m.UpdateFolderCommand) Response {
|
||||
@@ -116,7 +100,7 @@ func UpdateFolder(c *middleware.Context, cmd m.UpdateFolderCommand) Response {
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dashFolder.Id, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashFolder.Id, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return folderGuardianResponse(err)
|
||||
}
|
||||
@@ -140,7 +124,7 @@ func UpdateFolder(c *middleware.Context, cmd m.UpdateFolderCommand) Response {
|
||||
return toFolderError(err)
|
||||
}
|
||||
|
||||
return Json(200, toDto(guardian, folder))
|
||||
return Json(200, toFolderDto(&guardian, folder))
|
||||
}
|
||||
|
||||
func DeleteFolder(c *middleware.Context) Response {
|
||||
@@ -149,7 +133,7 @@ func DeleteFolder(c *middleware.Context) Response {
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dashFolder.Id, c.OrgId, c.SignedInUser)
|
||||
guardian := guardian.New(dashFolder.Id, c.OrgId, c.SignedInUser)
|
||||
if canSave, err := guardian.CanSave(); err != nil || !canSave {
|
||||
return folderGuardianResponse(err)
|
||||
}
|
||||
@@ -163,10 +147,10 @@ func DeleteFolder(c *middleware.Context) Response {
|
||||
return Json(200, resp)
|
||||
}
|
||||
|
||||
func toDto(guardian *guardian.DashboardGuardian, folder *m.Dashboard) dtos.Folder {
|
||||
canEdit, _ := guardian.CanEdit()
|
||||
canSave, _ := guardian.CanSave()
|
||||
canAdmin, _ := guardian.CanAdmin()
|
||||
func toFolderDto(g *guardian.DashboardGuardian, folder *m.Dashboard) dtos.Folder {
|
||||
canEdit, _ := g.CanEdit()
|
||||
canSave, _ := g.CanSave()
|
||||
canAdmin, _ := g.CanAdmin()
|
||||
|
||||
// Finding creator and last updater of the folder
|
||||
updater, creator := "Anonymous", "Anonymous"
|
||||
|
||||
@@ -168,7 +168,7 @@ func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand)
|
||||
|
||||
cmd := plugins.ImportDashboardCommand{
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
User: c.SignedInUser,
|
||||
PluginId: apiCmd.PluginId,
|
||||
Path: apiCmd.Path,
|
||||
Inputs: apiCmd.Inputs,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
)
|
||||
|
||||
@@ -15,11 +16,16 @@ func Search(c *middleware.Context) {
|
||||
starred := c.Query("starred")
|
||||
limit := c.QueryInt("limit")
|
||||
dashboardType := c.Query("type")
|
||||
permission := models.PERMISSION_VIEW
|
||||
|
||||
if limit == 0 {
|
||||
limit = 1000
|
||||
}
|
||||
|
||||
if c.Query("permission") == "Edit" {
|
||||
permission = models.PERMISSION_EDIT
|
||||
}
|
||||
|
||||
dbids := make([]int64, 0)
|
||||
for _, id := range c.QueryStrings("dashboardIds") {
|
||||
dashboardId, err := strconv.ParseInt(id, 10, 64)
|
||||
@@ -46,6 +52,7 @@ func Search(c *middleware.Context) {
|
||||
DashboardIds: dbids,
|
||||
Type: dashboardType,
|
||||
FolderIds: folderIds,
|
||||
Permission: permission,
|
||||
}
|
||||
|
||||
err := bus.Dispatch(&searchQuery)
|
||||
|
||||
@@ -29,9 +29,14 @@ func AddTeamMember(c *middleware.Context, cmd m.AddTeamMemberCommand) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrTeamMemberAlreadyAdded {
|
||||
return ApiError(400, "User is already added to this team", err)
|
||||
if err == m.ErrTeamNotFound {
|
||||
return ApiError(404, "Team not found", nil)
|
||||
}
|
||||
|
||||
if err == m.ErrTeamMemberAlreadyAdded {
|
||||
return ApiError(400, "User is already added to this team", nil)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to add Member to Team", err)
|
||||
}
|
||||
|
||||
@@ -43,6 +48,14 @@ func AddTeamMember(c *middleware.Context, cmd m.AddTeamMemberCommand) Response {
|
||||
// DELETE /api/teams/:teamId/members/:userId
|
||||
func RemoveTeamMember(c *middleware.Context) Response {
|
||||
if err := bus.Dispatch(&m.RemoveTeamMemberCommand{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId"), UserId: c.ParamsInt64(":userId")}); err != nil {
|
||||
if err == m.ErrTeamNotFound {
|
||||
return ApiError(404, "Team not found", nil)
|
||||
}
|
||||
|
||||
if err == m.ErrTeamMemberNotFound {
|
||||
return ApiError(404, "Team member not found", nil)
|
||||
}
|
||||
|
||||
return ApiError(500, "Failed to remove Member from Team", err)
|
||||
}
|
||||
return ApiSuccess("Team Member removed")
|
||||
|
||||
@@ -94,7 +94,7 @@ func InstallPlugin(pluginName, version string, c CommandLine) error {
|
||||
|
||||
res, _ := s.ReadPlugin(pluginFolder, pluginName)
|
||||
for _, v := range res.Dependencies.Plugins {
|
||||
InstallPlugin(v.Id, version, c)
|
||||
InstallPlugin(v.Id, "", c)
|
||||
logger.Infof("Installed dependency: %v ✔\n", v.Id)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ func notAuthorized(c *Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.SetCookie("redirect_to", url.QueryEscape(setting.AppSubUrl+c.Req.RequestURI), 0, setting.AppSubUrl+"/")
|
||||
c.SetCookie("redirect_to", url.QueryEscape(setting.AppSubUrl+c.Req.RequestURI), 0, setting.AppSubUrl+"/", nil, false, true)
|
||||
|
||||
c.Redirect(setting.AppSubUrl + "/login")
|
||||
}
|
||||
|
||||
|
||||
@@ -115,11 +115,11 @@ func Recovery() macaron.Handler {
|
||||
c.Data["Title"] = "Server Error"
|
||||
c.Data["AppSubUrl"] = setting.AppSubUrl
|
||||
|
||||
if theErr, ok := err.(error); ok {
|
||||
c.Data["Title"] = theErr.Error()
|
||||
}
|
||||
|
||||
if setting.Env == setting.DEV {
|
||||
if theErr, ok := err.(error); ok {
|
||||
c.Data["Title"] = theErr.Error()
|
||||
}
|
||||
|
||||
c.Data["ErrorMsg"] = string(stack)
|
||||
}
|
||||
|
||||
|
||||
@@ -166,8 +166,9 @@ type GetAlertsQuery struct {
|
||||
DashboardId int64
|
||||
PanelId int64
|
||||
Limit int64
|
||||
User *SignedInUser
|
||||
|
||||
Result []*Alert
|
||||
Result []*AlertListItemDTO
|
||||
}
|
||||
|
||||
type GetAllAlertsQuery struct {
|
||||
@@ -187,6 +188,21 @@ type GetAlertStatesForDashboardQuery struct {
|
||||
Result []*AlertStateInfoDTO
|
||||
}
|
||||
|
||||
type AlertListItemDTO struct {
|
||||
Id int64 `json:"id"`
|
||||
DashboardId int64 `json:"dashboardId"`
|
||||
DashboardUid string `json:"dashboardUid"`
|
||||
DashboardSlug string `json:"dashboardSlug"`
|
||||
PanelId int64 `json:"panelId"`
|
||||
Name string `json:"name"`
|
||||
State AlertStateType `json:"state"`
|
||||
NewStateDate time.Time `json:"newStateDate"`
|
||||
EvalDate time.Time `json:"evalDate"`
|
||||
EvalData *simplejson.Json `json:"evalData"`
|
||||
ExecutionError string `json:"executionError"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type AlertStateInfoDTO struct {
|
||||
Id int64 `json:"id"`
|
||||
DashboardId int64 `json:"dashboardId"`
|
||||
@@ -194,3 +210,17 @@ type AlertStateInfoDTO struct {
|
||||
State AlertStateType `json:"state"`
|
||||
NewStateDate time.Time `json:"newStateDate"`
|
||||
}
|
||||
|
||||
// "Internal" commands
|
||||
|
||||
type UpdateDashboardAlertsCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
Dashboard *Dashboard
|
||||
}
|
||||
|
||||
type ValidateDashboardAlertsCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
Dashboard *Dashboard
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ type DashboardAcl struct {
|
||||
}
|
||||
|
||||
type DashboardAclInfoDTO struct {
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"-"`
|
||||
DashboardId int64 `json:"dashboardId"`
|
||||
|
||||
@@ -75,21 +74,6 @@ type UpdateDashboardAclCommand struct {
|
||||
Items []*DashboardAcl
|
||||
}
|
||||
|
||||
type SetDashboardAclCommand struct {
|
||||
DashboardId int64
|
||||
OrgId int64
|
||||
UserId int64
|
||||
TeamId int64
|
||||
Permission PermissionType
|
||||
|
||||
Result DashboardAcl
|
||||
}
|
||||
|
||||
type RemoveDashboardAclCommand struct {
|
||||
AclId int64
|
||||
OrgId int64
|
||||
}
|
||||
|
||||
//
|
||||
// QUERIES
|
||||
//
|
||||
|
||||
@@ -13,22 +13,26 @@ import (
|
||||
|
||||
// Typed errors
|
||||
var (
|
||||
ErrDashboardNotFound = errors.New("Dashboard not found")
|
||||
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
|
||||
ErrDashboardWithSameUIDExists = errors.New("A dashboard with the same uid already exists")
|
||||
ErrDashboardWithSameNameInFolderExists = errors.New("A dashboard with the same name in the folder already exists")
|
||||
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
|
||||
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
|
||||
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
|
||||
ErrDashboardContainsInvalidAlertData = errors.New("Invalid alert data. Cannot save dashboard")
|
||||
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
|
||||
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
|
||||
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
|
||||
ErrDashboardExistingCannotChangeToDashboard = errors.New("An existing folder cannot be changed to a dashboard")
|
||||
ErrDashboardTypeMismatch = errors.New("Dashboard cannot be changed to a folder")
|
||||
ErrDashboardFolderWithSameNameAsDashboard = errors.New("Folder name cannot be the same as one of its dashboards")
|
||||
ErrDashboardWithSameNameAsFolder = errors.New("Dashboard name cannot be the same as folder")
|
||||
RootFolderName = "General"
|
||||
ErrDashboardNotFound = errors.New("Dashboard not found")
|
||||
ErrDashboardFolderNotFound = errors.New("Folder not found")
|
||||
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
|
||||
ErrDashboardWithSameUIDExists = errors.New("A dashboard with the same uid already exists")
|
||||
ErrDashboardWithSameNameInFolderExists = errors.New("A dashboard with the same name in the folder already exists")
|
||||
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
|
||||
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
|
||||
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
|
||||
ErrDashboardContainsInvalidAlertData = errors.New("Invalid alert data. Cannot save dashboard")
|
||||
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
|
||||
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
|
||||
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
|
||||
ErrDashboardTypeMismatch = errors.New("Dashboard cannot be changed to a folder")
|
||||
ErrDashboardFolderWithSameNameAsDashboard = errors.New("Folder name cannot be the same as one of its dashboards")
|
||||
ErrDashboardWithSameNameAsFolder = errors.New("Dashboard name cannot be the same as folder")
|
||||
ErrDashboardFolderNameExists = errors.New("A folder with that name already exists")
|
||||
ErrDashboardUpdateAccessDenied = errors.New("Access denied to save dashboard")
|
||||
ErrDashboardInvalidUid = errors.New("uid contains illegal characters")
|
||||
ErrDashboardUidToLong = errors.New("uid to long. max 40 characters")
|
||||
RootFolderName = "General"
|
||||
)
|
||||
|
||||
type UpdatePluginDashboardError struct {
|
||||
@@ -74,6 +78,25 @@ func (d *Dashboard) SetId(id int64) {
|
||||
d.Data.Set("id", id)
|
||||
}
|
||||
|
||||
func (d *Dashboard) SetUid(uid string) {
|
||||
d.Uid = uid
|
||||
d.Data.Set("uid", uid)
|
||||
}
|
||||
|
||||
func (d *Dashboard) SetVersion(version int) {
|
||||
d.Version = version
|
||||
d.Data.Set("version", version)
|
||||
}
|
||||
|
||||
// GetDashboardIdForSavePermissionCheck return the dashboard id to be used for checking permission of dashboard
|
||||
func (d *Dashboard) GetDashboardIdForSavePermissionCheck() int64 {
|
||||
if d.Id == 0 {
|
||||
return d.FolderId
|
||||
}
|
||||
|
||||
return d.Id
|
||||
}
|
||||
|
||||
// NewDashboard creates a new dashboard
|
||||
func NewDashboard(title string) *Dashboard {
|
||||
dash := &Dashboard{}
|
||||
@@ -92,6 +115,7 @@ func NewDashboardFolder(title string) *Dashboard {
|
||||
folder.IsFolder = true
|
||||
folder.Data.Set("schemaVersion", 16)
|
||||
folder.Data.Set("version", 0)
|
||||
folder.IsFolder = true
|
||||
return folder
|
||||
}
|
||||
|
||||
@@ -229,7 +253,7 @@ type DashboardProvisioning struct {
|
||||
DashboardId int64
|
||||
Name string
|
||||
ExternalId string
|
||||
Updated time.Time
|
||||
Updated int64
|
||||
}
|
||||
|
||||
type SaveProvisionedDashboardCommand struct {
|
||||
@@ -244,6 +268,12 @@ type DeleteDashboardCommand struct {
|
||||
OrgId int64
|
||||
}
|
||||
|
||||
type ValidateDashboardBeforeSaveCommand struct {
|
||||
OrgId int64
|
||||
Dashboard *Dashboard
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
//
|
||||
// QUERIES
|
||||
//
|
||||
|
||||
@@ -93,19 +93,3 @@ type UpdateFolderCommand struct {
|
||||
|
||||
Result *Folder
|
||||
}
|
||||
|
||||
//
|
||||
// QUERIES
|
||||
//
|
||||
|
||||
type DashboardFolder struct {
|
||||
Id int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
type GetFoldersForSignedInUserQuery struct {
|
||||
OrgId int64
|
||||
SignedInUser *SignedInUser
|
||||
Title string
|
||||
Result []*DashboardFolder
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ type LoginAttempt struct {
|
||||
Id int64
|
||||
Username string
|
||||
IpAddress string
|
||||
Created time.Time
|
||||
Created int64
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
|
||||
// Typed errors
|
||||
var (
|
||||
ErrTeamNotFound = errors.New("Team not found")
|
||||
ErrTeamNameTaken = errors.New("Team name is taken")
|
||||
ErrTeamNotFound = errors.New("Team not found")
|
||||
ErrTeamNameTaken = errors.New("Team name is taken")
|
||||
ErrTeamMemberNotFound = errors.New("Team member not found")
|
||||
)
|
||||
|
||||
// Team model
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
)
|
||||
|
||||
type ImportDashboardCommand struct {
|
||||
@@ -17,7 +18,7 @@ type ImportDashboardCommand struct {
|
||||
Overwrite bool
|
||||
|
||||
OrgId int64
|
||||
UserId int64
|
||||
User *m.SignedInUser
|
||||
PluginId string
|
||||
Result *PluginDashboardInfoDTO
|
||||
}
|
||||
@@ -66,23 +67,32 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
|
||||
saveCmd := m.SaveDashboardCommand{
|
||||
Dashboard: generatedDash,
|
||||
OrgId: cmd.OrgId,
|
||||
UserId: cmd.UserId,
|
||||
UserId: cmd.User.UserId,
|
||||
Overwrite: cmd.Overwrite,
|
||||
PluginId: cmd.PluginId,
|
||||
FolderId: dashboard.FolderId,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&saveCmd); err != nil {
|
||||
dto := &dashboards.SaveDashboardDTO{
|
||||
OrgId: cmd.OrgId,
|
||||
Dashboard: saveCmd.GetDashboardModel(),
|
||||
Overwrite: saveCmd.Overwrite,
|
||||
User: cmd.User,
|
||||
}
|
||||
|
||||
savedDash, err := dashboards.NewService().SaveDashboard(dto)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = &PluginDashboardInfoDTO{
|
||||
PluginId: cmd.PluginId,
|
||||
Title: dashboard.Title,
|
||||
Title: savedDash.Title,
|
||||
Path: cmd.Path,
|
||||
Revision: dashboard.Data.Get("revision").MustInt64(1),
|
||||
ImportedUri: "db/" + saveCmd.Result.Slug,
|
||||
ImportedUrl: saveCmd.Result.GetUrl(),
|
||||
Revision: savedDash.Data.Get("revision").MustInt64(1),
|
||||
ImportedUri: "db/" + savedDash.Slug,
|
||||
ImportedUrl: savedDash.GetUrl(),
|
||||
ImportedRevision: dashboard.Data.Get("revision").MustInt64(1),
|
||||
Imported: true,
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -15,19 +15,15 @@ import (
|
||||
|
||||
func TestDashboardImport(t *testing.T) {
|
||||
pluginScenario("When importing a plugin dashboard", t, func() {
|
||||
var importedDash *m.Dashboard
|
||||
|
||||
bus.AddHandler("test", func(cmd *m.SaveDashboardCommand) error {
|
||||
importedDash = cmd.GetDashboardModel()
|
||||
cmd.Result = importedDash
|
||||
return nil
|
||||
})
|
||||
origNewDashboardService := dashboards.NewService
|
||||
mock := &dashboards.FakeDashboardService{}
|
||||
dashboards.MockDashboardService(mock)
|
||||
|
||||
cmd := ImportDashboardCommand{
|
||||
PluginId: "test-app",
|
||||
Path: "dashboards/connections.json",
|
||||
OrgId: 1,
|
||||
UserId: 1,
|
||||
User: &m.SignedInUser{UserId: 1, OrgRole: m.ROLE_ADMIN},
|
||||
Inputs: []ImportDashboardInput{
|
||||
{Name: "*", Type: "datasource", Value: "graphite"},
|
||||
},
|
||||
@@ -37,18 +33,22 @@ func TestDashboardImport(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("should install dashboard", func() {
|
||||
So(importedDash, ShouldNotBeNil)
|
||||
So(cmd.Result, ShouldNotBeNil)
|
||||
|
||||
resultStr, _ := importedDash.Data.EncodePretty()
|
||||
resultStr, _ := mock.SavedDashboards[0].Dashboard.Data.EncodePretty()
|
||||
expectedBytes, _ := ioutil.ReadFile("../../tests/test-app/dashboards/connections_result.json")
|
||||
expectedJson, _ := simplejson.NewJson(expectedBytes)
|
||||
expectedStr, _ := expectedJson.EncodePretty()
|
||||
|
||||
So(string(resultStr), ShouldEqual, string(expectedStr))
|
||||
|
||||
panel := importedDash.Data.Get("rows").GetIndex(0).Get("panels").GetIndex(0)
|
||||
panel := mock.SavedDashboards[0].Dashboard.Data.Get("rows").GetIndex(0).Get("panels").GetIndex(0)
|
||||
So(panel.Get("datasource").MustString(), ShouldEqual, "graphite")
|
||||
})
|
||||
|
||||
Reset(func() {
|
||||
dashboards.NewService = origNewDashboardService
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When evaling dashboard template", t, func() {
|
||||
@@ -84,7 +84,6 @@ func TestDashboardImport(t *testing.T) {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func pluginScenario(desc string, t *testing.T, fn func()) {
|
||||
|
||||
@@ -47,7 +47,7 @@ func autoUpdateAppDashboard(pluginDashInfo *PluginDashboardInfoDTO, orgId int64)
|
||||
PluginId: pluginDashInfo.PluginId,
|
||||
Overwrite: true,
|
||||
Dashboard: dash.Data,
|
||||
UserId: 0,
|
||||
User: &m.SignedInUser{UserId: 0, OrgRole: m.ROLE_ADMIN},
|
||||
Path: pluginDashInfo.Path,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,24 +5,12 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type UpdateDashboardAlertsCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
Dashboard *m.Dashboard
|
||||
}
|
||||
|
||||
type ValidateDashboardAlertsCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
Dashboard *m.Dashboard
|
||||
}
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("alerting", updateDashboardAlerts)
|
||||
bus.AddHandler("alerting", validateDashboardAlerts)
|
||||
}
|
||||
|
||||
func validateDashboardAlerts(cmd *ValidateDashboardAlertsCommand) error {
|
||||
func validateDashboardAlerts(cmd *m.ValidateDashboardAlertsCommand) error {
|
||||
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
|
||||
|
||||
if _, err := extractor.GetAlerts(); err != nil {
|
||||
@@ -32,7 +20,7 @@ func validateDashboardAlerts(cmd *ValidateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateDashboardAlerts(cmd *UpdateDashboardAlertsCommand) error {
|
||||
func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error {
|
||||
saveAlerts := m.SaveAlertsCommand{
|
||||
OrgId: cmd.OrgId,
|
||||
UserId: cmd.UserId,
|
||||
|
||||
231
pkg/services/dashboards/dashboard_service.go
Normal file
231
pkg/services/dashboards/dashboard_service.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// DashboardService service for operating on dashboards
|
||||
type DashboardService interface {
|
||||
SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error)
|
||||
}
|
||||
|
||||
// DashboardProvisioningService service for operating on provisioned dashboards
|
||||
type DashboardProvisioningService interface {
|
||||
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
|
||||
SaveFolderForProvisionedDashboards(*SaveDashboardDTO) (*models.Dashboard, error)
|
||||
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
|
||||
}
|
||||
|
||||
// NewService factory for creating a new dashboard service
|
||||
var NewService = func() DashboardService {
|
||||
return &dashboardServiceImpl{}
|
||||
}
|
||||
|
||||
// NewProvisioningService factory for creating a new dashboard provisioning service
|
||||
var NewProvisioningService = func() DashboardProvisioningService {
|
||||
return &dashboardServiceImpl{}
|
||||
}
|
||||
|
||||
type SaveDashboardDTO struct {
|
||||
OrgId int64
|
||||
UpdatedAt time.Time
|
||||
User *models.SignedInUser
|
||||
Message string
|
||||
Overwrite bool
|
||||
Dashboard *models.Dashboard
|
||||
}
|
||||
|
||||
type dashboardServiceImpl struct{}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO) (*models.SaveDashboardCommand, error) {
|
||||
dash := dto.Dashboard
|
||||
|
||||
dash.Title = strings.TrimSpace(dash.Title)
|
||||
dash.Data.Set("title", dash.Title)
|
||||
dash.SetUid(strings.TrimSpace(dash.Uid))
|
||||
|
||||
if dash.Title == "" {
|
||||
return nil, models.ErrDashboardTitleEmpty
|
||||
}
|
||||
|
||||
if dash.IsFolder && dash.FolderId > 0 {
|
||||
return nil, models.ErrDashboardFolderCannotHaveParent
|
||||
}
|
||||
|
||||
if dash.IsFolder && strings.ToLower(dash.Title) == strings.ToLower(models.RootFolderName) {
|
||||
return nil, models.ErrDashboardFolderNameExists
|
||||
}
|
||||
|
||||
if !util.IsValidShortUid(dash.Uid) {
|
||||
return nil, models.ErrDashboardInvalidUid
|
||||
} else if len(dash.Uid) > 40 {
|
||||
return nil, models.ErrDashboardUidToLong
|
||||
}
|
||||
|
||||
validateAlertsCmd := models.ValidateDashboardAlertsCommand{
|
||||
OrgId: dto.OrgId,
|
||||
Dashboard: dash,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
|
||||
return nil, models.ErrDashboardContainsInvalidAlertData
|
||||
}
|
||||
|
||||
validateBeforeSaveCmd := models.ValidateDashboardBeforeSaveCommand{
|
||||
OrgId: dto.OrgId,
|
||||
Dashboard: dash,
|
||||
Overwrite: dto.Overwrite,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&validateBeforeSaveCmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
guard := guardian.New(dash.GetDashboardIdForSavePermissionCheck(), dto.OrgId, dto.User)
|
||||
if canSave, err := guard.CanSave(); err != nil || !canSave {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, models.ErrDashboardUpdateAccessDenied
|
||||
}
|
||||
|
||||
cmd := &models.SaveDashboardCommand{
|
||||
Dashboard: dash.Data,
|
||||
Message: dto.Message,
|
||||
OrgId: dto.OrgId,
|
||||
Overwrite: dto.Overwrite,
|
||||
UserId: dto.User.UserId,
|
||||
FolderId: dash.FolderId,
|
||||
IsFolder: dash.IsFolder,
|
||||
}
|
||||
|
||||
if !dto.UpdatedAt.IsZero() {
|
||||
cmd.UpdatedAt = dto.UpdatedAt
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error {
|
||||
alertCmd := models.UpdateDashboardAlertsCommand{
|
||||
OrgId: dto.OrgId,
|
||||
UserId: dto.User.UserId,
|
||||
Dashboard: cmd.Result,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&alertCmd); err != nil {
|
||||
return models.ErrDashboardFailedToUpdateAlertData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||
dto.User = &models.SignedInUser{
|
||||
UserId: 0,
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
}
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveCmd := &models.SaveProvisionedDashboardCommand{
|
||||
DashboardCmd: cmd,
|
||||
DashboardProvisioning: provisioning,
|
||||
}
|
||||
|
||||
// dashboard
|
||||
err = bus.Dispatch(saveCmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//alerts
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
dto.User = &models.SignedInUser{
|
||||
UserId: 0,
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
}
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
func (dr *dashboardServiceImpl) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
type FakeDashboardService struct {
|
||||
SaveDashboardResult *models.Dashboard
|
||||
SaveDashboardError error
|
||||
SavedDashboards []*SaveDashboardDTO
|
||||
}
|
||||
|
||||
func (s *FakeDashboardService) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
s.SavedDashboards = append(s.SavedDashboards, dto)
|
||||
|
||||
if s.SaveDashboardResult == nil && s.SaveDashboardError == nil {
|
||||
s.SaveDashboardResult = dto.Dashboard
|
||||
}
|
||||
|
||||
return s.SaveDashboardResult, s.SaveDashboardError
|
||||
}
|
||||
|
||||
func MockDashboardService(mock *FakeDashboardService) {
|
||||
NewService = func() DashboardService {
|
||||
return mock
|
||||
}
|
||||
}
|
||||
144
pkg/services/dashboards/dashboard_service_test.go
Normal file
144
pkg/services/dashboards/dashboard_service_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestDashboardService(t *testing.T) {
|
||||
Convey("Dashboard service tests", t, func() {
|
||||
service := dashboardServiceImpl{}
|
||||
|
||||
origNewDashboardGuardian := guardian.New
|
||||
mockDashboardGuardian(&fakeDashboardGuardian{canSave: true})
|
||||
|
||||
Convey("Save dashboard validation", func() {
|
||||
dto := &SaveDashboardDTO{}
|
||||
|
||||
Convey("When saving a dashboard with empty title it should return error", func() {
|
||||
titles := []string{"", " ", " \t "}
|
||||
|
||||
for _, title := range titles {
|
||||
dto.Dashboard = models.NewDashboard(title)
|
||||
_, err := service.SaveDashboard(dto)
|
||||
So(err, ShouldEqual, models.ErrDashboardTitleEmpty)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should return validation error if it's a folder and have a folder id", func() {
|
||||
dto.Dashboard = models.NewDashboardFolder("Folder")
|
||||
dto.Dashboard.FolderId = 1
|
||||
_, err := service.SaveDashboard(dto)
|
||||
So(err, ShouldEqual, models.ErrDashboardFolderCannotHaveParent)
|
||||
})
|
||||
|
||||
Convey("Should return validation error if folder is named General", func() {
|
||||
dto.Dashboard = models.NewDashboardFolder("General")
|
||||
_, err := service.SaveDashboard(dto)
|
||||
So(err, ShouldEqual, models.ErrDashboardFolderNameExists)
|
||||
})
|
||||
|
||||
Convey("When saving a dashboard should validate uid", func() {
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
Uid string
|
||||
Error error
|
||||
}{
|
||||
{Uid: "", Error: nil},
|
||||
{Uid: " ", Error: nil},
|
||||
{Uid: " \t ", Error: nil},
|
||||
{Uid: "asdf90_-", Error: nil},
|
||||
{Uid: "asdf/90", Error: models.ErrDashboardInvalidUid},
|
||||
{Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil},
|
||||
{Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: models.ErrDashboardUidToLong},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
dto.Dashboard = models.NewDashboard("title")
|
||||
dto.Dashboard.SetUid(tc.Uid)
|
||||
dto.User = &models.SignedInUser{}
|
||||
|
||||
_, err := service.buildSaveDashboardCommand(dto)
|
||||
So(err, ShouldEqual, tc.Error)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should return validation error if alert data is invalid", func() {
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||
return errors.New("error")
|
||||
})
|
||||
|
||||
dto.Dashboard = models.NewDashboard("Dash")
|
||||
_, err := service.SaveDashboard(dto)
|
||||
So(err, ShouldEqual, models.ErrDashboardContainsInvalidAlertData)
|
||||
})
|
||||
})
|
||||
|
||||
Reset(func() {
|
||||
guardian.New = origNewDashboardGuardian
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func mockDashboardGuardian(mock *fakeDashboardGuardian) {
|
||||
guardian.New = func(dashId int64, orgId int64, user *models.SignedInUser) guardian.DashboardGuardian {
|
||||
mock.orgId = orgId
|
||||
mock.dashId = dashId
|
||||
mock.user = user
|
||||
return mock
|
||||
}
|
||||
}
|
||||
|
||||
type fakeDashboardGuardian struct {
|
||||
dashId int64
|
||||
orgId int64
|
||||
user *models.SignedInUser
|
||||
canSave bool
|
||||
canEdit bool
|
||||
canView bool
|
||||
canAdmin bool
|
||||
hasPermission bool
|
||||
checkPermissionBeforeUpdate bool
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CanSave() (bool, error) {
|
||||
return g.canSave, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CanEdit() (bool, error) {
|
||||
return g.canEdit, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CanView() (bool, error) {
|
||||
return g.canView, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CanAdmin() (bool, error) {
|
||||
return g.canAdmin, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) HasPermission(permission models.PermissionType) (bool, error) {
|
||||
return g.hasPermission, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) CheckPermissionBeforeUpdate(permission models.PermissionType, updatePermissions []*models.DashboardAcl) (bool, error) {
|
||||
return g.checkPermissionBeforeUpdate, nil
|
||||
}
|
||||
|
||||
func (g *fakeDashboardGuardian) GetAcl() ([]*models.DashboardAclInfoDTO, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
SaveDashboard(*SaveDashboardDTO) (*models.Dashboard, error)
|
||||
SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
|
||||
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
|
||||
}
|
||||
|
||||
var repositoryInstance Repository
|
||||
|
||||
func GetRepository() Repository {
|
||||
return repositoryInstance
|
||||
}
|
||||
|
||||
func SetRepository(rep Repository) {
|
||||
repositoryInstance = rep
|
||||
}
|
||||
|
||||
type SaveDashboardDTO struct {
|
||||
OrgId int64
|
||||
UpdatedAt time.Time
|
||||
UserId int64
|
||||
Message string
|
||||
Overwrite bool
|
||||
Dashboard *models.Dashboard
|
||||
}
|
||||
|
||||
type DashboardRepository struct{}
|
||||
|
||||
func (dr *DashboardRepository) 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
|
||||
}
|
||||
|
||||
func (dr *DashboardRepository) buildSaveDashboardCommand(dto *SaveDashboardDTO) (*models.SaveDashboardCommand, error) {
|
||||
dashboard := dto.Dashboard
|
||||
|
||||
if dashboard.Title == "" {
|
||||
return nil, models.ErrDashboardTitleEmpty
|
||||
}
|
||||
|
||||
validateAlertsCmd := alerting.ValidateDashboardAlertsCommand{
|
||||
OrgId: dto.OrgId,
|
||||
Dashboard: dashboard,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
|
||||
return nil, models.ErrDashboardContainsInvalidAlertData
|
||||
}
|
||||
|
||||
cmd := &models.SaveDashboardCommand{
|
||||
Dashboard: dashboard.Data,
|
||||
Message: dto.Message,
|
||||
OrgId: dto.OrgId,
|
||||
Overwrite: dto.Overwrite,
|
||||
UserId: dto.UserId,
|
||||
FolderId: dashboard.FolderId,
|
||||
IsFolder: dashboard.IsFolder,
|
||||
}
|
||||
|
||||
if !dto.UpdatedAt.IsZero() {
|
||||
cmd.UpdatedAt = dto.UpdatedAt
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardRepository) updateAlerting(cmd *models.SaveDashboardCommand, dto *SaveDashboardDTO) error {
|
||||
alertCmd := alerting.UpdateDashboardAlertsCommand{
|
||||
OrgId: dto.OrgId,
|
||||
UserId: dto.UserId,
|
||||
Dashboard: cmd.Result,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&alertCmd); err != nil {
|
||||
return models.ErrDashboardFailedToUpdateAlertData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dr *DashboardRepository) SaveProvisionedDashboard(dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveCmd := &models.SaveProvisionedDashboardCommand{
|
||||
DashboardCmd: cmd,
|
||||
DashboardProvisioning: provisioning,
|
||||
}
|
||||
|
||||
// dashboard
|
||||
err = bus.Dispatch(saveCmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//alerts
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardRepository) SaveDashboard(dto *SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
cmd, err := dr.buildSaveDashboardCommand(dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dr.updateAlerting(cmd, dto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Result, nil
|
||||
}
|
||||
@@ -7,7 +7,18 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type DashboardGuardian struct {
|
||||
// DashboardGuardian to be used for guard against operations without access on dashboard and acl
|
||||
type DashboardGuardian interface {
|
||||
CanSave() (bool, error)
|
||||
CanEdit() (bool, error)
|
||||
CanView() (bool, error)
|
||||
CanAdmin() (bool, error)
|
||||
HasPermission(permission m.PermissionType) (bool, error)
|
||||
CheckPermissionBeforeUpdate(permission m.PermissionType, updatePermissions []*m.DashboardAcl) (bool, error)
|
||||
GetAcl() ([]*m.DashboardAclInfoDTO, error)
|
||||
}
|
||||
|
||||
type dashboardGuardianImpl struct {
|
||||
user *m.SignedInUser
|
||||
dashId int64
|
||||
orgId int64
|
||||
@@ -16,8 +27,9 @@ type DashboardGuardian struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewDashboardGuardian(dashId int64, orgId int64, user *m.SignedInUser) *DashboardGuardian {
|
||||
return &DashboardGuardian{
|
||||
// New factory for creating a new dashboard guardian instance
|
||||
var New = func(dashId int64, orgId int64, user *m.SignedInUser) DashboardGuardian {
|
||||
return &dashboardGuardianImpl{
|
||||
user: user,
|
||||
dashId: dashId,
|
||||
orgId: orgId,
|
||||
@@ -25,11 +37,11 @@ func NewDashboardGuardian(dashId int64, orgId int64, user *m.SignedInUser) *Dash
|
||||
}
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanSave() (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CanSave() (bool, error) {
|
||||
return g.HasPermission(m.PERMISSION_EDIT)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanEdit() (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CanEdit() (bool, error) {
|
||||
if setting.ViewersCanEdit {
|
||||
return g.HasPermission(m.PERMISSION_VIEW)
|
||||
}
|
||||
@@ -37,15 +49,15 @@ func (g *DashboardGuardian) CanEdit() (bool, error) {
|
||||
return g.HasPermission(m.PERMISSION_EDIT)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanView() (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CanView() (bool, error) {
|
||||
return g.HasPermission(m.PERMISSION_VIEW)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanAdmin() (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CanAdmin() (bool, error) {
|
||||
return g.HasPermission(m.PERMISSION_ADMIN)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, error) {
|
||||
func (g *dashboardGuardianImpl) HasPermission(permission m.PermissionType) (bool, error) {
|
||||
if g.user.OrgRole == m.ROLE_ADMIN {
|
||||
return true, nil
|
||||
}
|
||||
@@ -58,7 +70,7 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er
|
||||
return g.checkAcl(permission, acl)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) checkAcl(permission m.PermissionType, acl []*m.DashboardAclInfoDTO) (bool, error) {
|
||||
func (g *dashboardGuardianImpl) checkAcl(permission m.PermissionType, acl []*m.DashboardAclInfoDTO) (bool, error) {
|
||||
orgRole := g.user.OrgRole
|
||||
teamAclItems := []*m.DashboardAclInfoDTO{}
|
||||
|
||||
@@ -106,27 +118,7 @@ func (g *DashboardGuardian) checkAcl(permission m.PermissionType, acl []*m.Dashb
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CheckPermissionBeforeRemove(permission m.PermissionType, aclIdToRemove int64) (bool, error) {
|
||||
if g.user.OrgRole == m.ROLE_ADMIN {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
acl, err := g.GetAcl()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for i, p := range acl {
|
||||
if p.Id == aclIdToRemove {
|
||||
acl = append(acl[:i], acl[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return g.checkAcl(permission, acl)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CheckPermissionBeforeUpdate(permission m.PermissionType, updatePermissions []*m.DashboardAcl) (bool, error) {
|
||||
func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission m.PermissionType, updatePermissions []*m.DashboardAcl) (bool, error) {
|
||||
if g.user.OrgRole == m.ROLE_ADMIN {
|
||||
return true, nil
|
||||
}
|
||||
@@ -141,7 +133,7 @@ func (g *DashboardGuardian) CheckPermissionBeforeUpdate(permission m.PermissionT
|
||||
}
|
||||
|
||||
// GetAcl returns dashboard acl
|
||||
func (g *DashboardGuardian) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
func (g *dashboardGuardianImpl) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
if g.acl != nil {
|
||||
return g.acl, nil
|
||||
}
|
||||
@@ -155,7 +147,7 @@ func (g *DashboardGuardian) GetAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
return g.acl, nil
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) getTeams() ([]*m.Team, error) {
|
||||
func (g *dashboardGuardianImpl) getTeams() ([]*m.Team, error) {
|
||||
if g.groups != nil {
|
||||
return g.groups, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package dashboards
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -14,34 +15,66 @@ type configReader struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (cr *configReader) parseConfigs(file os.FileInfo) ([]*DashboardsAsConfig, error) {
|
||||
filename, _ := filepath.Abs(filepath.Join(cr.path, file.Name()))
|
||||
yamlFile, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiVersion := &ConfigVersion{ApiVersion: 0}
|
||||
yaml.Unmarshal(yamlFile, &apiVersion)
|
||||
|
||||
if apiVersion.ApiVersion > 0 {
|
||||
|
||||
v1 := &DashboardAsConfigV1{}
|
||||
err := yaml.Unmarshal(yamlFile, &v1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v1 != nil {
|
||||
return v1.mapToDashboardAsConfig(), nil
|
||||
}
|
||||
|
||||
} else {
|
||||
var v0 []*DashboardsAsConfigV0
|
||||
err := yaml.Unmarshal(yamlFile, &v0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v0 != nil {
|
||||
cr.log.Warn("[Deprecated] the dashboard provisioning config is outdated. please upgrade", "filename", filename)
|
||||
return mapV0ToDashboardAsConfig(v0), nil
|
||||
}
|
||||
}
|
||||
|
||||
return []*DashboardsAsConfig{}, nil
|
||||
}
|
||||
|
||||
func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) {
|
||||
var dashboards []*DashboardsAsConfig
|
||||
|
||||
files, err := ioutil.ReadDir(cr.path)
|
||||
|
||||
if err != nil {
|
||||
cr.log.Error("cant read dashboard provisioning files from directory", "path", cr.path)
|
||||
return dashboards, nil
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml") {
|
||||
if (!strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml")) || file.Name() == "sample.yaml" {
|
||||
continue
|
||||
}
|
||||
|
||||
filename, _ := filepath.Abs(filepath.Join(cr.path, file.Name()))
|
||||
yamlFile, err := ioutil.ReadFile(filename)
|
||||
parsedDashboards, err := cr.parseConfigs(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
var dashCfg []*DashboardsAsConfig
|
||||
err = yaml.Unmarshal(yamlFile, &dashCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(parsedDashboards) > 0 {
|
||||
dashboards = append(dashboards, parsedDashboards...)
|
||||
}
|
||||
|
||||
dashboards = append(dashboards, dashCfg...)
|
||||
}
|
||||
|
||||
for i := range dashboards {
|
||||
|
||||
@@ -9,48 +9,33 @@ import (
|
||||
|
||||
var (
|
||||
simpleDashboardConfig string = "./test-configs/dashboards-from-disk"
|
||||
oldVersion string = "./test-configs/version-0"
|
||||
brokenConfigs string = "./test-configs/broken-configs"
|
||||
)
|
||||
|
||||
func TestDashboardsAsConfig(t *testing.T) {
|
||||
Convey("Dashboards as configuration", t, func() {
|
||||
logger := log.New("test-logger")
|
||||
|
||||
Convey("Can read config file", func() {
|
||||
|
||||
cfgProvider := configReader{path: simpleDashboardConfig, log: log.New("test-logger")}
|
||||
Convey("Can read config file version 1 format", func() {
|
||||
cfgProvider := configReader{path: simpleDashboardConfig, log: logger}
|
||||
cfg, err := cfgProvider.readConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
}
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(cfg), ShouldEqual, 2)
|
||||
validateDashboardAsConfig(cfg)
|
||||
})
|
||||
|
||||
ds := cfg[0]
|
||||
Convey("Can read config file in version 0 format", func() {
|
||||
cfgProvider := configReader{path: oldVersion, log: logger}
|
||||
cfg, err := cfgProvider.readConfig()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(ds.Name, ShouldEqual, "general dashboards")
|
||||
So(ds.Type, ShouldEqual, "file")
|
||||
So(ds.OrgId, ShouldEqual, 2)
|
||||
So(ds.Folder, ShouldEqual, "developers")
|
||||
So(ds.Editable, ShouldBeTrue)
|
||||
|
||||
So(len(ds.Options), ShouldEqual, 1)
|
||||
So(ds.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
|
||||
|
||||
ds2 := cfg[1]
|
||||
|
||||
So(ds2.Name, ShouldEqual, "default")
|
||||
So(ds2.Type, ShouldEqual, "file")
|
||||
So(ds2.OrgId, ShouldEqual, 1)
|
||||
So(ds2.Folder, ShouldEqual, "")
|
||||
So(ds2.Editable, ShouldBeFalse)
|
||||
|
||||
So(len(ds2.Options), ShouldEqual, 1)
|
||||
So(ds2.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
|
||||
validateDashboardAsConfig(cfg)
|
||||
})
|
||||
|
||||
Convey("Should skip invalid path", func() {
|
||||
|
||||
cfgProvider := configReader{path: "/invalid-directory", log: log.New("test-logger")}
|
||||
cfgProvider := configReader{path: "/invalid-directory", log: logger}
|
||||
cfg, err := cfgProvider.readConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
@@ -61,7 +46,7 @@ func TestDashboardsAsConfig(t *testing.T) {
|
||||
|
||||
Convey("Should skip broken config files", func() {
|
||||
|
||||
cfgProvider := configReader{path: brokenConfigs, log: log.New("test-logger")}
|
||||
cfgProvider := configReader{path: brokenConfigs, log: logger}
|
||||
cfg, err := cfgProvider.readConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
@@ -71,3 +56,26 @@ func TestDashboardsAsConfig(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
func validateDashboardAsConfig(cfg []*DashboardsAsConfig) {
|
||||
So(len(cfg), ShouldEqual, 2)
|
||||
|
||||
ds := cfg[0]
|
||||
So(ds.Name, ShouldEqual, "general dashboards")
|
||||
So(ds.Type, ShouldEqual, "file")
|
||||
So(ds.OrgId, ShouldEqual, 2)
|
||||
So(ds.Folder, ShouldEqual, "developers")
|
||||
So(ds.Editable, ShouldBeTrue)
|
||||
So(len(ds.Options), ShouldEqual, 1)
|
||||
So(ds.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
|
||||
So(ds.DisableDeletion, ShouldBeTrue)
|
||||
|
||||
ds2 := cfg[1]
|
||||
So(ds2.Name, ShouldEqual, "default")
|
||||
So(ds2.Type, ShouldEqual, "file")
|
||||
So(ds2.OrgId, ShouldEqual, 1)
|
||||
So(ds2.Folder, ShouldEqual, "")
|
||||
So(ds2.Editable, ShouldBeFalse)
|
||||
So(len(ds2.Options), ShouldEqual, 1)
|
||||
So(ds2.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
|
||||
So(ds2.DisableDeletion, ShouldBeFalse)
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ var (
|
||||
)
|
||||
|
||||
type fileReader struct {
|
||||
Cfg *DashboardsAsConfig
|
||||
Path string
|
||||
log log.Logger
|
||||
dashboardRepo dashboards.Repository
|
||||
Cfg *DashboardsAsConfig
|
||||
Path string
|
||||
log log.Logger
|
||||
dashboardService dashboards.DashboardProvisioningService
|
||||
}
|
||||
|
||||
func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReader, error) {
|
||||
@@ -48,10 +48,10 @@ func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReade
|
||||
}
|
||||
|
||||
return &fileReader{
|
||||
Cfg: cfg,
|
||||
Path: path,
|
||||
log: log,
|
||||
dashboardRepo: dashboards.GetRepository(),
|
||||
Cfg: cfg,
|
||||
Path: path,
|
||||
log: log,
|
||||
dashboardService: dashboards.NewProvisioningService(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -89,12 +89,12 @@ func (fr *fileReader) startWalkingDisk() error {
|
||||
}
|
||||
}
|
||||
|
||||
folderId, err := getOrCreateFolderId(fr.Cfg, fr.dashboardRepo)
|
||||
folderId, err := getOrCreateFolderId(fr.Cfg, fr.dashboardService)
|
||||
if err != nil && err != ErrFolderNameMissing {
|
||||
return err
|
||||
}
|
||||
|
||||
provisionedDashboardRefs, err := getProvisionedDashboardByPath(fr.dashboardRepo, fr.Cfg.Name)
|
||||
provisionedDashboardRefs, err := getProvisionedDashboardByPath(fr.dashboardService, fr.Cfg.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -105,6 +105,27 @@ func (fr *fileReader) startWalkingDisk() error {
|
||||
return err
|
||||
}
|
||||
|
||||
fr.deleteDashboardIfFileIsMissing(provisionedDashboardRefs, filesFoundOnDisk)
|
||||
|
||||
sanityChecker := newProvisioningSanityChecker(fr.Cfg.Name)
|
||||
|
||||
// save dashboards based on json files
|
||||
for path, fileInfo := range filesFoundOnDisk {
|
||||
provisioningMetadata, err := fr.saveDashboard(path, folderId, fileInfo, provisionedDashboardRefs)
|
||||
sanityChecker.track(provisioningMetadata)
|
||||
if err != nil {
|
||||
fr.log.Error("failed to save dashboard", "error", err)
|
||||
}
|
||||
}
|
||||
sanityChecker.logWarnings(fr.log)
|
||||
|
||||
return nil
|
||||
}
|
||||
func (fr *fileReader) deleteDashboardIfFileIsMissing(provisionedDashboardRefs map[string]*models.DashboardProvisioning, filesFoundOnDisk map[string]os.FileInfo) {
|
||||
if fr.Cfg.DisableDeletion {
|
||||
return
|
||||
}
|
||||
|
||||
// find dashboards to delete since json file is missing
|
||||
var dashboardToDelete []int64
|
||||
for path, provisioningData := range provisionedDashboardRefs {
|
||||
@@ -113,7 +134,6 @@ func (fr *fileReader) startWalkingDisk() error {
|
||||
dashboardToDelete = append(dashboardToDelete, provisioningData.DashboardId)
|
||||
}
|
||||
}
|
||||
|
||||
// delete dashboard that are missing json file
|
||||
for _, dashboardId := range dashboardToDelete {
|
||||
fr.log.Debug("deleting provisioned dashboard. missing on disk", "id", dashboardId)
|
||||
@@ -123,38 +143,35 @@ func (fr *fileReader) startWalkingDisk() error {
|
||||
fr.log.Error("failed to delete dashboard", "id", cmd.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// save dashboards based on json files
|
||||
for path, fileInfo := range filesFoundOnDisk {
|
||||
err = fr.saveDashboard(path, folderId, fileInfo, provisionedDashboardRefs)
|
||||
if err != nil {
|
||||
fr.log.Error("failed to save dashboard", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.FileInfo, provisionedDashboardRefs map[string]*models.DashboardProvisioning) error {
|
||||
func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.FileInfo, provisionedDashboardRefs map[string]*models.DashboardProvisioning) (provisioningMetadata, error) {
|
||||
provisioningMetadata := provisioningMetadata{}
|
||||
resolvedFileInfo, err := resolveSymlink(fileInfo, path)
|
||||
if err != nil {
|
||||
return err
|
||||
return provisioningMetadata, err
|
||||
}
|
||||
|
||||
provisionedData, alreadyProvisioned := provisionedDashboardRefs[path]
|
||||
if alreadyProvisioned && provisionedData.Updated.Unix() == resolvedFileInfo.ModTime().Unix() {
|
||||
return nil // dashboard is already in sync with the database
|
||||
}
|
||||
upToDate := alreadyProvisioned && provisionedData.Updated == resolvedFileInfo.ModTime().Unix()
|
||||
|
||||
dash, err := fr.readDashboardFromFile(path, resolvedFileInfo.ModTime(), folderId)
|
||||
if err != nil {
|
||||
fr.log.Error("failed to load dashboard from ", "file", path, "error", err)
|
||||
return nil
|
||||
return provisioningMetadata, nil
|
||||
}
|
||||
|
||||
// keeps track of what uid's and title's we have already provisioned
|
||||
provisioningMetadata.uid = dash.Dashboard.Uid
|
||||
provisioningMetadata.title = dash.Dashboard.Title
|
||||
|
||||
if upToDate {
|
||||
return provisioningMetadata, nil
|
||||
}
|
||||
|
||||
if dash.Dashboard.Id != 0 {
|
||||
fr.log.Error("provisioned dashboard json files cannot contain id")
|
||||
return nil
|
||||
return provisioningMetadata, nil
|
||||
}
|
||||
|
||||
if alreadyProvisioned {
|
||||
@@ -162,13 +179,13 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil
|
||||
}
|
||||
|
||||
fr.log.Debug("saving new dashboard", "file", path)
|
||||
dp := &models.DashboardProvisioning{ExternalId: path, Name: fr.Cfg.Name, Updated: resolvedFileInfo.ModTime()}
|
||||
_, err = fr.dashboardRepo.SaveProvisionedDashboard(dash, dp)
|
||||
return err
|
||||
dp := &models.DashboardProvisioning{ExternalId: path, Name: fr.Cfg.Name, Updated: resolvedFileInfo.ModTime().Unix()}
|
||||
_, err = fr.dashboardService.SaveProvisionedDashboard(dash, dp)
|
||||
return provisioningMetadata, err
|
||||
}
|
||||
|
||||
func getProvisionedDashboardByPath(repo dashboards.Repository, name string) (map[string]*models.DashboardProvisioning, error) {
|
||||
arr, err := repo.GetProvisionedDashboardData(name)
|
||||
func getProvisionedDashboardByPath(service dashboards.DashboardProvisioningService, name string) (map[string]*models.DashboardProvisioning, error) {
|
||||
arr, err := service.GetProvisionedDashboardData(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -181,7 +198,7 @@ func getProvisionedDashboardByPath(repo dashboards.Repository, name string) (map
|
||||
return byPath, nil
|
||||
}
|
||||
|
||||
func getOrCreateFolderId(cfg *DashboardsAsConfig, repo dashboards.Repository) (int64, error) {
|
||||
func getOrCreateFolderId(cfg *DashboardsAsConfig, service dashboards.DashboardProvisioningService) (int64, error) {
|
||||
if cfg.Folder == "" {
|
||||
return 0, ErrFolderNameMissing
|
||||
}
|
||||
@@ -196,11 +213,11 @@ func getOrCreateFolderId(cfg *DashboardsAsConfig, repo dashboards.Repository) (i
|
||||
// dashboard folder not found. create one.
|
||||
if err == models.ErrDashboardNotFound {
|
||||
dash := &dashboards.SaveDashboardDTO{}
|
||||
dash.Dashboard = models.NewDashboard(cfg.Folder)
|
||||
dash.Dashboard = models.NewDashboardFolder(cfg.Folder)
|
||||
dash.Dashboard.IsFolder = true
|
||||
dash.Overwrite = true
|
||||
dash.OrgId = cfg.OrgId
|
||||
dbDash, err := repo.SaveDashboard(dash)
|
||||
dbDash, err := service.SaveFolderForProvisionedDashboards(dash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -280,3 +297,46 @@ func (fr *fileReader) readDashboardFromFile(path string, lastModified time.Time,
|
||||
|
||||
return dash, nil
|
||||
}
|
||||
|
||||
type provisioningMetadata struct {
|
||||
uid string
|
||||
title string
|
||||
}
|
||||
|
||||
func newProvisioningSanityChecker(provisioningProvider string) provisioningSanityChecker {
|
||||
return provisioningSanityChecker{
|
||||
provisioningProvider: provisioningProvider,
|
||||
uidUsage: map[string]uint8{},
|
||||
titleUsage: map[string]uint8{}}
|
||||
}
|
||||
|
||||
type provisioningSanityChecker struct {
|
||||
provisioningProvider string
|
||||
uidUsage map[string]uint8
|
||||
titleUsage map[string]uint8
|
||||
}
|
||||
|
||||
func (checker provisioningSanityChecker) track(pm provisioningMetadata) {
|
||||
if len(pm.uid) > 0 {
|
||||
checker.uidUsage[pm.uid] += 1
|
||||
}
|
||||
if len(pm.title) > 0 {
|
||||
checker.titleUsage[pm.title] += 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (checker provisioningSanityChecker) logWarnings(log log.Logger) {
|
||||
for uid, times := range checker.uidUsage {
|
||||
if times > 1 {
|
||||
log.Error("the same 'uid' is used more than once", "uid", uid, "provider", checker.provisioningProvider)
|
||||
}
|
||||
}
|
||||
|
||||
for title, times := range checker.titleUsage {
|
||||
if times > 1 {
|
||||
log.Error("the same 'title' is used more than once", "title", title, "provider", checker.provisioningProvider)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,16 +19,16 @@ var (
|
||||
brokenDashboards string = "./test-dashboards/broken-dashboards"
|
||||
oneDashboard string = "./test-dashboards/one-dashboard"
|
||||
|
||||
fakeRepo *fakeDashboardRepo
|
||||
fakeService *fakeDashboardProvisioningService
|
||||
)
|
||||
|
||||
func TestDashboardFileReader(t *testing.T) {
|
||||
Convey("Dashboard file reader", t, func() {
|
||||
bus.ClearBusHandlers()
|
||||
fakeRepo = &fakeDashboardRepo{}
|
||||
origNewDashboardProvisioningService := dashboards.NewProvisioningService
|
||||
fakeService = mockDashboardProvisioningService()
|
||||
|
||||
bus.AddHandler("test", mockGetDashboardQuery)
|
||||
dashboards.SetRepository(fakeRepo)
|
||||
logger := log.New("test.logger")
|
||||
|
||||
Convey("Reading dashboards from disk", func() {
|
||||
@@ -54,7 +54,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
folders := 0
|
||||
dashboards := 0
|
||||
|
||||
for _, i := range fakeRepo.inserted {
|
||||
for _, i := range fakeService.inserted {
|
||||
if i.Dashboard.IsFolder {
|
||||
folders++
|
||||
} else {
|
||||
@@ -71,7 +71,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
|
||||
stat, _ := os.Stat(oneDashboard + "/dashboard1.json")
|
||||
|
||||
fakeRepo.getDashboard = append(fakeRepo.getDashboard, &models.Dashboard{
|
||||
fakeService.getDashboard = append(fakeService.getDashboard, &models.Dashboard{
|
||||
Updated: stat.ModTime().AddDate(0, 0, -1),
|
||||
Slug: "grafana",
|
||||
})
|
||||
@@ -82,7 +82,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
err = reader.startWalkingDisk()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(fakeRepo.inserted), ShouldEqual, 1)
|
||||
So(len(fakeService.inserted), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Invalid configuration should return error", func() {
|
||||
@@ -116,7 +116,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := getOrCreateFolderId(cfg, fakeRepo)
|
||||
_, err := getOrCreateFolderId(cfg, fakeService)
|
||||
So(err, ShouldEqual, ErrFolderNameMissing)
|
||||
})
|
||||
|
||||
@@ -131,15 +131,15 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
folderId, err := getOrCreateFolderId(cfg, fakeRepo)
|
||||
folderId, err := getOrCreateFolderId(cfg, fakeService)
|
||||
So(err, ShouldBeNil)
|
||||
inserted := false
|
||||
for _, d := range fakeRepo.inserted {
|
||||
for _, d := range fakeService.inserted {
|
||||
if d.Dashboard.IsFolder && d.Dashboard.Id == folderId {
|
||||
inserted = true
|
||||
}
|
||||
}
|
||||
So(len(fakeRepo.inserted), ShouldEqual, 1)
|
||||
So(len(fakeService.inserted), ShouldEqual, 1)
|
||||
So(inserted, ShouldBeTrue)
|
||||
})
|
||||
|
||||
@@ -180,6 +180,10 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
So(reader.Path, ShouldEqual, defaultDashboards)
|
||||
})
|
||||
})
|
||||
|
||||
Reset(func() {
|
||||
dashboards.NewProvisioningService = origNewDashboardProvisioningService
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -212,29 +216,37 @@ func (ffi FakeFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeDashboardRepo struct {
|
||||
func mockDashboardProvisioningService() *fakeDashboardProvisioningService {
|
||||
mock := fakeDashboardProvisioningService{}
|
||||
dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService {
|
||||
return &mock
|
||||
}
|
||||
return &mock
|
||||
}
|
||||
|
||||
type fakeDashboardProvisioningService struct {
|
||||
inserted []*dashboards.SaveDashboardDTO
|
||||
provisioned []*models.DashboardProvisioning
|
||||
getDashboard []*models.Dashboard
|
||||
}
|
||||
|
||||
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
repo.inserted = append(repo.inserted, json)
|
||||
return json.Dashboard, nil
|
||||
func (s *fakeDashboardProvisioningService) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||
return s.provisioned, nil
|
||||
}
|
||||
|
||||
func (repo *fakeDashboardRepo) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
|
||||
return repo.provisioned, nil
|
||||
func (s *fakeDashboardProvisioningService) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||
s.inserted = append(s.inserted, dto)
|
||||
s.provisioned = append(s.provisioned, provisioning)
|
||||
return dto.Dashboard, nil
|
||||
}
|
||||
|
||||
func (repo *fakeDashboardRepo) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
|
||||
repo.inserted = append(repo.inserted, dto)
|
||||
repo.provisioned = append(repo.provisioned, provisioning)
|
||||
func (s *fakeDashboardProvisioningService) SaveFolderForProvisionedDashboards(dto *dashboards.SaveDashboardDTO) (*models.Dashboard, error) {
|
||||
s.inserted = append(s.inserted, dto)
|
||||
return dto.Dashboard, nil
|
||||
}
|
||||
|
||||
func mockGetDashboardQuery(cmd *models.GetDashboardQuery) error {
|
||||
for _, d := range fakeRepo.getDashboard {
|
||||
for _, d := range fakeService.getDashboard {
|
||||
if d.Slug == cmd.Slug {
|
||||
cmd.Result = d
|
||||
return nil
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'general dashboards'
|
||||
org_id: 2
|
||||
orgId: 2
|
||||
folder: 'developers'
|
||||
editable: true
|
||||
disableDeletion: true
|
||||
type: file
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'gasdf'
|
||||
orgId: 2
|
||||
folder: 'developers'
|
||||
editable: true
|
||||
type: file
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
@@ -0,0 +1,13 @@
|
||||
- name: 'general dashboards'
|
||||
org_id: 2
|
||||
folder: 'developers'
|
||||
editable: true
|
||||
disableDeletion: true
|
||||
type: file
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
|
||||
- name: 'default'
|
||||
type: file
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
@@ -10,12 +10,41 @@ import (
|
||||
)
|
||||
|
||||
type DashboardsAsConfig struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
OrgId int64 `json:"org_id" yaml:"org_id"`
|
||||
Folder string `json:"folder" yaml:"folder"`
|
||||
Editable bool `json:"editable" yaml:"editable"`
|
||||
Options map[string]interface{} `json:"options" yaml:"options"`
|
||||
Name string
|
||||
Type string
|
||||
OrgId int64
|
||||
Folder string
|
||||
Editable bool
|
||||
Options map[string]interface{}
|
||||
DisableDeletion bool
|
||||
}
|
||||
|
||||
type DashboardsAsConfigV0 struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
OrgId int64 `json:"org_id" yaml:"org_id"`
|
||||
Folder string `json:"folder" yaml:"folder"`
|
||||
Editable bool `json:"editable" yaml:"editable"`
|
||||
Options map[string]interface{} `json:"options" yaml:"options"`
|
||||
DisableDeletion bool `json:"disableDeletion" yaml:"disableDeletion"`
|
||||
}
|
||||
|
||||
type ConfigVersion struct {
|
||||
ApiVersion int64 `json:"apiVersion" yaml:"apiVersion"`
|
||||
}
|
||||
|
||||
type DashboardAsConfigV1 struct {
|
||||
Providers []*DashboardProviderConfigs `json:"providers" yaml:"providers"`
|
||||
}
|
||||
|
||||
type DashboardProviderConfigs struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
OrgId int64 `json:"orgId" yaml:"orgId"`
|
||||
Folder string `json:"folder" yaml:"folder"`
|
||||
Editable bool `json:"editable" yaml:"editable"`
|
||||
Options map[string]interface{} `json:"options" yaml:"options"`
|
||||
DisableDeletion bool `json:"disableDeletion" yaml:"disableDeletion"`
|
||||
}
|
||||
|
||||
func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *DashboardsAsConfig, folderId int64) (*dashboards.SaveDashboardDTO, error) {
|
||||
@@ -36,3 +65,39 @@ func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *Das
|
||||
|
||||
return dash, nil
|
||||
}
|
||||
|
||||
func mapV0ToDashboardAsConfig(v0 []*DashboardsAsConfigV0) []*DashboardsAsConfig {
|
||||
var r []*DashboardsAsConfig
|
||||
|
||||
for _, v := range v0 {
|
||||
r = append(r, &DashboardsAsConfig{
|
||||
Name: v.Name,
|
||||
Type: v.Type,
|
||||
OrgId: v.OrgId,
|
||||
Folder: v.Folder,
|
||||
Editable: v.Editable,
|
||||
Options: v.Options,
|
||||
DisableDeletion: v.DisableDeletion,
|
||||
})
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (dc *DashboardAsConfigV1) mapToDashboardAsConfig() []*DashboardsAsConfig {
|
||||
var r []*DashboardsAsConfig
|
||||
|
||||
for _, v := range dc.Providers {
|
||||
r = append(r, &DashboardsAsConfig{
|
||||
Name: v.Name,
|
||||
Type: v.Type,
|
||||
OrgId: v.OrgId,
|
||||
Folder: v.Folder,
|
||||
Editable: v.Editable,
|
||||
Options: v.Options,
|
||||
DisableDeletion: v.DisableDeletion,
|
||||
})
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
113
pkg/services/provisioning/datasources/config_reader.go
Normal file
113
pkg/services/provisioning/datasources/config_reader.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package datasources
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type configReader struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (cr *configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) {
|
||||
var datasources []*DatasourcesAsConfig
|
||||
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
cr.log.Error("cant read datasource provisioning files from directory", "path", path)
|
||||
return datasources, nil
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if (strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml")) && file.Name() != "sample.yaml" {
|
||||
datasource, err := cr.parseDatasourceConfig(path, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if datasource != nil {
|
||||
datasources = append(datasources, datasource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = validateDefaultUniqueness(datasources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return datasources, nil
|
||||
}
|
||||
|
||||
func (cr *configReader) parseDatasourceConfig(path string, file os.FileInfo) (*DatasourcesAsConfig, error) {
|
||||
filename, _ := filepath.Abs(filepath.Join(path, file.Name()))
|
||||
yamlFile, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiVersion *ConfigVersion
|
||||
err = yaml.Unmarshal(yamlFile, &apiVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if apiVersion == nil {
|
||||
apiVersion = &ConfigVersion{ApiVersion: 0}
|
||||
}
|
||||
|
||||
if apiVersion.ApiVersion > 0 {
|
||||
var v1 *DatasourcesAsConfigV1
|
||||
err = yaml.Unmarshal(yamlFile, &v1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v1.mapToDatasourceFromConfig(apiVersion.ApiVersion), nil
|
||||
}
|
||||
|
||||
var v0 *DatasourcesAsConfigV0
|
||||
err = yaml.Unmarshal(yamlFile, &v0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cr.log.Warn("[Deprecated] the datasource provisioning config is outdated. please upgrade", "filename", filename)
|
||||
|
||||
return v0.mapToDatasourceFromConfig(apiVersion.ApiVersion), nil
|
||||
}
|
||||
|
||||
func validateDefaultUniqueness(datasources []*DatasourcesAsConfig) error {
|
||||
defaultCount := 0
|
||||
for i := range datasources {
|
||||
if datasources[i].Datasources == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ds := range datasources[i].Datasources {
|
||||
if ds.OrgId == 0 {
|
||||
ds.OrgId = 1
|
||||
}
|
||||
|
||||
if ds.IsDefault {
|
||||
defaultCount++
|
||||
if defaultCount > 1 {
|
||||
return ErrInvalidConfigToManyDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ds := range datasources[i].DeleteDatasources {
|
||||
if ds.OrgId == 0 {
|
||||
ds.OrgId = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -17,6 +17,7 @@ var (
|
||||
twoDatasourcesConfigPurgeOthers string = "./test-configs/insert-two-delete-two"
|
||||
doubleDatasourcesConfig string = "./test-configs/double-default"
|
||||
allProperties string = "./test-configs/all-properties"
|
||||
versionZero string = "./test-configs/version-0"
|
||||
brokenYaml string = "./test-configs/broken-yaml"
|
||||
|
||||
fakeRepo *fakeRepository
|
||||
@@ -130,7 +131,7 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
So(len(cfg), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("can read all properties", func() {
|
||||
Convey("can read all properties from version 1", func() {
|
||||
cfgProvifer := &configReader{log: log.New("test logger")}
|
||||
cfg, err := cfgProvifer.readConfig(allProperties)
|
||||
if err != nil {
|
||||
@@ -140,38 +141,65 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
So(len(cfg), ShouldEqual, 2)
|
||||
|
||||
dsCfg := cfg[0]
|
||||
ds := dsCfg.Datasources[0]
|
||||
|
||||
So(ds.Name, ShouldEqual, "name")
|
||||
So(ds.Type, ShouldEqual, "type")
|
||||
So(ds.Access, ShouldEqual, models.DS_ACCESS_PROXY)
|
||||
So(ds.OrgId, ShouldEqual, 2)
|
||||
So(ds.Url, ShouldEqual, "url")
|
||||
So(ds.User, ShouldEqual, "user")
|
||||
So(ds.Password, ShouldEqual, "password")
|
||||
So(ds.Database, ShouldEqual, "database")
|
||||
So(ds.BasicAuth, ShouldBeTrue)
|
||||
So(ds.BasicAuthUser, ShouldEqual, "basic_auth_user")
|
||||
So(ds.BasicAuthPassword, ShouldEqual, "basic_auth_password")
|
||||
So(ds.WithCredentials, ShouldBeTrue)
|
||||
So(ds.IsDefault, ShouldBeTrue)
|
||||
So(ds.Editable, ShouldBeTrue)
|
||||
So(dsCfg.ApiVersion, ShouldEqual, 1)
|
||||
|
||||
So(len(ds.JsonData), ShouldBeGreaterThan, 2)
|
||||
So(ds.JsonData["graphiteVersion"], ShouldEqual, "1.1")
|
||||
So(ds.JsonData["tlsAuth"], ShouldEqual, true)
|
||||
So(ds.JsonData["tlsAuthWithCACert"], ShouldEqual, true)
|
||||
validateDatasource(dsCfg)
|
||||
validateDeleteDatasources(dsCfg)
|
||||
})
|
||||
|
||||
So(len(ds.SecureJsonData), ShouldBeGreaterThan, 2)
|
||||
So(ds.SecureJsonData["tlsCACert"], ShouldEqual, "MjNOcW9RdkbUDHZmpco2HCYzVq9dE+i6Yi+gmUJotq5CDA==")
|
||||
So(ds.SecureJsonData["tlsClientCert"], ShouldEqual, "ckN0dGlyMXN503YNfjTcf9CV+GGQneN+xmAclQ==")
|
||||
So(ds.SecureJsonData["tlsClientKey"], ShouldEqual, "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A==")
|
||||
Convey("can read all properties from version 0", func() {
|
||||
cfgProvifer := &configReader{log: log.New("test logger")}
|
||||
cfg, err := cfgProvifer.readConfig(versionZero)
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
}
|
||||
|
||||
dstwo := cfg[1].Datasources[0]
|
||||
So(dstwo.Name, ShouldEqual, "name2")
|
||||
So(len(cfg), ShouldEqual, 1)
|
||||
|
||||
dsCfg := cfg[0]
|
||||
|
||||
So(dsCfg.ApiVersion, ShouldEqual, 0)
|
||||
|
||||
validateDatasource(dsCfg)
|
||||
validateDeleteDatasources(dsCfg)
|
||||
})
|
||||
})
|
||||
}
|
||||
func validateDeleteDatasources(dsCfg *DatasourcesAsConfig) {
|
||||
So(len(dsCfg.DeleteDatasources), ShouldEqual, 1)
|
||||
deleteDs := dsCfg.DeleteDatasources[0]
|
||||
So(deleteDs.Name, ShouldEqual, "old-graphite3")
|
||||
So(deleteDs.OrgId, ShouldEqual, 2)
|
||||
}
|
||||
func validateDatasource(dsCfg *DatasourcesAsConfig) {
|
||||
ds := dsCfg.Datasources[0]
|
||||
So(ds.Name, ShouldEqual, "name")
|
||||
So(ds.Type, ShouldEqual, "type")
|
||||
So(ds.Access, ShouldEqual, models.DS_ACCESS_PROXY)
|
||||
So(ds.OrgId, ShouldEqual, 2)
|
||||
So(ds.Url, ShouldEqual, "url")
|
||||
So(ds.User, ShouldEqual, "user")
|
||||
So(ds.Password, ShouldEqual, "password")
|
||||
So(ds.Database, ShouldEqual, "database")
|
||||
So(ds.BasicAuth, ShouldBeTrue)
|
||||
So(ds.BasicAuthUser, ShouldEqual, "basic_auth_user")
|
||||
So(ds.BasicAuthPassword, ShouldEqual, "basic_auth_password")
|
||||
So(ds.WithCredentials, ShouldBeTrue)
|
||||
So(ds.IsDefault, ShouldBeTrue)
|
||||
So(ds.Editable, ShouldBeTrue)
|
||||
So(ds.Version, ShouldEqual, 10)
|
||||
|
||||
So(len(ds.JsonData), ShouldBeGreaterThan, 2)
|
||||
So(ds.JsonData["graphiteVersion"], ShouldEqual, "1.1")
|
||||
So(ds.JsonData["tlsAuth"], ShouldEqual, true)
|
||||
So(ds.JsonData["tlsAuthWithCACert"], ShouldEqual, true)
|
||||
|
||||
So(len(ds.SecureJsonData), ShouldBeGreaterThan, 2)
|
||||
So(ds.SecureJsonData["tlsCACert"], ShouldEqual, "MjNOcW9RdkbUDHZmpco2HCYzVq9dE+i6Yi+gmUJotq5CDA==")
|
||||
So(ds.SecureJsonData["tlsClientCert"], ShouldEqual, "ckN0dGlyMXN503YNfjTcf9CV+GGQneN+xmAclQ==")
|
||||
So(ds.SecureJsonData["tlsClientKey"], ShouldEqual, "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A==")
|
||||
}
|
||||
|
||||
type fakeRepository struct {
|
||||
inserted []*models.AddDataSourceCommand
|
||||
@@ -2,16 +2,12 @@ package datasources
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -94,65 +90,3 @@ func (dc *DatasourceProvisioner) deleteDatasources(dsToDelete []*DeleteDatasourc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type configReader struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (cr *configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) {
|
||||
var datasources []*DatasourcesAsConfig
|
||||
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
cr.log.Error("cant read datasource provisioning files from directory", "path", path)
|
||||
return datasources, nil
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml") {
|
||||
filename, _ := filepath.Abs(filepath.Join(path, file.Name()))
|
||||
yamlFile, err := ioutil.ReadFile(filename)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datasource *DatasourcesAsConfig
|
||||
err = yaml.Unmarshal(yamlFile, &datasource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if datasource != nil {
|
||||
datasources = append(datasources, datasource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultCount := 0
|
||||
for i := range datasources {
|
||||
if datasources[i].Datasources == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ds := range datasources[i].Datasources {
|
||||
if ds.OrgId == 0 {
|
||||
ds.OrgId = 1
|
||||
}
|
||||
|
||||
if ds.IsDefault {
|
||||
defaultCount++
|
||||
if defaultCount > 1 {
|
||||
return nil, ErrInvalidConfigToManyDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ds := range datasources[i].DeleteDatasources {
|
||||
if ds.OrgId == 0 {
|
||||
ds.OrgId = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return datasources, nil
|
||||
}
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: name
|
||||
type: type
|
||||
access: proxy
|
||||
org_id: 2
|
||||
orgId: 2
|
||||
url: url
|
||||
password: password
|
||||
user: user
|
||||
database: database
|
||||
basic_auth: true
|
||||
basic_auth_user: basic_auth_user
|
||||
basic_auth_password: basic_auth_password
|
||||
with_credentials: true
|
||||
is_default: true
|
||||
json_data:
|
||||
basicAuth: true
|
||||
basicAuthUser: basic_auth_user
|
||||
basicAuthPassword: basic_auth_password
|
||||
withCredentials: true
|
||||
isDefault: true
|
||||
jsonData:
|
||||
graphiteVersion: "1.1"
|
||||
tlsAuth: true
|
||||
tlsAuthWithCACert: true
|
||||
secure_json_data:
|
||||
secureJsonData:
|
||||
tlsCACert: "MjNOcW9RdkbUDHZmpco2HCYzVq9dE+i6Yi+gmUJotq5CDA=="
|
||||
tlsClientCert: "ckN0dGlyMXN503YNfjTcf9CV+GGQneN+xmAclQ=="
|
||||
tlsClientKey: "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A=="
|
||||
editable: true
|
||||
version: 10
|
||||
|
||||
deleteDatasources:
|
||||
- name: old-graphite3
|
||||
orgId: 2
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Should not be included
|
||||
|
||||
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: name
|
||||
type: type
|
||||
access: proxy
|
||||
orgId: 2
|
||||
url: url
|
||||
password: password
|
||||
user: user
|
||||
database: database
|
||||
basicAuth: true
|
||||
basicAuthUser: basic_auth_user
|
||||
basicAuthPassword: basic_auth_password
|
||||
withCredentials: true
|
||||
jsonData:
|
||||
graphiteVersion: "1.1"
|
||||
tlsAuth: true
|
||||
tlsAuthWithCACert: true
|
||||
secureJsonData:
|
||||
tlsCACert: "MjNOcW9RdkbUDHZmpco2HCYzVq9dE+i6Yi+gmUJotq5CDA=="
|
||||
tlsClientCert: "ckN0dGlyMXN503YNfjTcf9CV+GGQneN+xmAclQ=="
|
||||
tlsClientKey: "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A=="
|
||||
editable: true
|
||||
version: 10
|
||||
|
||||
deleteDatasources:
|
||||
- name: old-graphite3
|
||||
orgId: 2
|
||||
@@ -3,5 +3,5 @@ datasources:
|
||||
- name: name2
|
||||
type: type2
|
||||
access: proxy
|
||||
org_id: 2
|
||||
orgId: 2
|
||||
url: url2
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
datasources:
|
||||
- name: name
|
||||
type: type
|
||||
access: proxy
|
||||
org_id: 2
|
||||
url: url
|
||||
password: password
|
||||
user: user
|
||||
database: database
|
||||
basic_auth: true
|
||||
basic_auth_user: basic_auth_user
|
||||
basic_auth_password: basic_auth_password
|
||||
with_credentials: true
|
||||
is_default: true
|
||||
json_data:
|
||||
graphiteVersion: "1.1"
|
||||
tlsAuth: true
|
||||
tlsAuthWithCACert: true
|
||||
secure_json_data:
|
||||
tlsCACert: "MjNOcW9RdkbUDHZmpco2HCYzVq9dE+i6Yi+gmUJotq5CDA=="
|
||||
tlsClientCert: "ckN0dGlyMXN503YNfjTcf9CV+GGQneN+xmAclQ=="
|
||||
tlsClientKey: "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A=="
|
||||
editable: true
|
||||
version: 10
|
||||
|
||||
delete_datasources:
|
||||
- name: old-graphite3
|
||||
org_id: 2
|
||||
@@ -1,22 +1,74 @@
|
||||
package datasources
|
||||
|
||||
import "github.com/grafana/grafana/pkg/models"
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
import "github.com/grafana/grafana/pkg/components/simplejson"
|
||||
|
||||
type ConfigVersion struct {
|
||||
ApiVersion int64 `json:"apiVersion" yaml:"apiVersion"`
|
||||
}
|
||||
|
||||
type DatasourcesAsConfig struct {
|
||||
Datasources []*DataSourceFromConfig `json:"datasources" yaml:"datasources"`
|
||||
DeleteDatasources []*DeleteDatasourceConfig `json:"delete_datasources" yaml:"delete_datasources"`
|
||||
ApiVersion int64
|
||||
|
||||
Datasources []*DataSourceFromConfig
|
||||
DeleteDatasources []*DeleteDatasourceConfig
|
||||
}
|
||||
|
||||
type DeleteDatasourceConfig struct {
|
||||
OrgId int64
|
||||
Name string
|
||||
}
|
||||
|
||||
type DataSourceFromConfig struct {
|
||||
OrgId int64
|
||||
Version int
|
||||
|
||||
Name string
|
||||
Type string
|
||||
Access string
|
||||
Url string
|
||||
Password string
|
||||
User string
|
||||
Database string
|
||||
BasicAuth bool
|
||||
BasicAuthUser string
|
||||
BasicAuthPassword string
|
||||
WithCredentials bool
|
||||
IsDefault bool
|
||||
JsonData map[string]interface{}
|
||||
SecureJsonData map[string]string
|
||||
Editable bool
|
||||
}
|
||||
|
||||
type DatasourcesAsConfigV0 struct {
|
||||
ConfigVersion
|
||||
|
||||
Datasources []*DataSourceFromConfigV0 `json:"datasources" yaml:"datasources"`
|
||||
DeleteDatasources []*DeleteDatasourceConfigV0 `json:"delete_datasources" yaml:"delete_datasources"`
|
||||
}
|
||||
|
||||
type DatasourcesAsConfigV1 struct {
|
||||
ConfigVersion
|
||||
|
||||
Datasources []*DataSourceFromConfigV1 `json:"datasources" yaml:"datasources"`
|
||||
DeleteDatasources []*DeleteDatasourceConfigV1 `json:"deleteDatasources" yaml:"deleteDatasources"`
|
||||
}
|
||||
|
||||
type DeleteDatasourceConfigV0 struct {
|
||||
OrgId int64 `json:"org_id" yaml:"org_id"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
}
|
||||
|
||||
type DataSourceFromConfig struct {
|
||||
OrgId int64 `json:"org_id" yaml:"org_id"`
|
||||
Version int `json:"version" yaml:"version"`
|
||||
type DeleteDatasourceConfigV1 struct {
|
||||
OrgId int64 `json:"orgId" yaml:"orgId"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
}
|
||||
|
||||
type DataSourceFromConfigV0 struct {
|
||||
OrgId int64 `json:"org_id" yaml:"org_id"`
|
||||
Version int `json:"version" yaml:"version"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
Access string `json:"access" yaml:"access"`
|
||||
@@ -34,6 +86,108 @@ type DataSourceFromConfig struct {
|
||||
Editable bool `json:"editable" yaml:"editable"`
|
||||
}
|
||||
|
||||
type DataSourceFromConfigV1 struct {
|
||||
OrgId int64 `json:"orgId" yaml:"orgId"`
|
||||
Version int `json:"version" yaml:"version"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Type string `json:"type" yaml:"type"`
|
||||
Access string `json:"access" yaml:"access"`
|
||||
Url string `json:"url" yaml:"url"`
|
||||
Password string `json:"password" yaml:"password"`
|
||||
User string `json:"user" yaml:"user"`
|
||||
Database string `json:"database" yaml:"database"`
|
||||
BasicAuth bool `json:"basicAuth" yaml:"basicAuth"`
|
||||
BasicAuthUser string `json:"basicAuthUser" yaml:"basicAuthUser"`
|
||||
BasicAuthPassword string `json:"basicAuthPassword" yaml:"basicAuthPassword"`
|
||||
WithCredentials bool `json:"withCredentials" yaml:"withCredentials"`
|
||||
IsDefault bool `json:"isDefault" yaml:"isDefault"`
|
||||
JsonData map[string]interface{} `json:"jsonData" yaml:"jsonData"`
|
||||
SecureJsonData map[string]string `json:"secureJsonData" yaml:"secureJsonData"`
|
||||
Editable bool `json:"editable" yaml:"editable"`
|
||||
}
|
||||
|
||||
func (cfg *DatasourcesAsConfigV1) mapToDatasourceFromConfig(apiVersion int64) *DatasourcesAsConfig {
|
||||
r := &DatasourcesAsConfig{}
|
||||
|
||||
r.ApiVersion = apiVersion
|
||||
|
||||
if cfg == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
for _, ds := range cfg.Datasources {
|
||||
r.Datasources = append(r.Datasources, &DataSourceFromConfig{
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
Type: ds.Type,
|
||||
Access: ds.Access,
|
||||
Url: ds.Url,
|
||||
Password: ds.Password,
|
||||
User: ds.User,
|
||||
Database: ds.Database,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
BasicAuthPassword: ds.BasicAuthPassword,
|
||||
WithCredentials: ds.WithCredentials,
|
||||
IsDefault: ds.IsDefault,
|
||||
JsonData: ds.JsonData,
|
||||
SecureJsonData: ds.SecureJsonData,
|
||||
Editable: ds.Editable,
|
||||
Version: ds.Version,
|
||||
})
|
||||
}
|
||||
|
||||
for _, ds := range cfg.DeleteDatasources {
|
||||
r.DeleteDatasources = append(r.DeleteDatasources, &DeleteDatasourceConfig{
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
})
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (cfg *DatasourcesAsConfigV0) mapToDatasourceFromConfig(apiVersion int64) *DatasourcesAsConfig {
|
||||
r := &DatasourcesAsConfig{}
|
||||
|
||||
r.ApiVersion = apiVersion
|
||||
|
||||
if cfg == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
for _, ds := range cfg.Datasources {
|
||||
r.Datasources = append(r.Datasources, &DataSourceFromConfig{
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
Type: ds.Type,
|
||||
Access: ds.Access,
|
||||
Url: ds.Url,
|
||||
Password: ds.Password,
|
||||
User: ds.User,
|
||||
Database: ds.Database,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
BasicAuthPassword: ds.BasicAuthPassword,
|
||||
WithCredentials: ds.WithCredentials,
|
||||
IsDefault: ds.IsDefault,
|
||||
JsonData: ds.JsonData,
|
||||
SecureJsonData: ds.SecureJsonData,
|
||||
Editable: ds.Editable,
|
||||
Version: ds.Version,
|
||||
})
|
||||
}
|
||||
|
||||
for _, ds := range cfg.DeleteDatasources {
|
||||
r.DeleteDatasources = append(r.DeleteDatasources, &DeleteDatasourceConfig{
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
})
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func createInsertCommand(ds *DataSourceFromConfig) *models.AddDataSourceCommand {
|
||||
jsonData := simplejson.New()
|
||||
if len(ds.JsonData) > 0 {
|
||||
|
||||
@@ -21,6 +21,7 @@ func searchHandler(query *Query) error {
|
||||
FolderIds: query.FolderIds,
|
||||
Tags: query.Tags,
|
||||
Limit: query.Limit,
|
||||
Permission: query.Permission,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&dashQuery); err != nil {
|
||||
|
||||
@@ -52,6 +52,7 @@ type Query struct {
|
||||
Type string
|
||||
DashboardIds []int64
|
||||
FolderIds []int64
|
||||
Permission models.PermissionType
|
||||
|
||||
Result HitList
|
||||
}
|
||||
@@ -66,7 +67,7 @@ type FindPersistedDashboardsQuery struct {
|
||||
FolderIds []int64
|
||||
Tags []string
|
||||
Limit int
|
||||
IsBrowse bool
|
||||
Permission models.PermissionType
|
||||
|
||||
Result HitList
|
||||
}
|
||||
|
||||
@@ -61,52 +61,61 @@ func deleteAlertByIdInternal(alertId int64, reason string, sess *DBSession) erro
|
||||
}
|
||||
|
||||
func HandleAlertsQuery(query *m.GetAlertsQuery) error {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
builder := SqlBuilder{}
|
||||
|
||||
sql.WriteString(`SELECT *
|
||||
from alert
|
||||
`)
|
||||
builder.Write(`SELECT
|
||||
alert.id,
|
||||
alert.dashboard_id,
|
||||
alert.panel_id,
|
||||
alert.name,
|
||||
alert.state,
|
||||
alert.new_state_date,
|
||||
alert.eval_date,
|
||||
alert.execution_error,
|
||||
dashboard.uid as dashboard_uid,
|
||||
dashboard.slug as dashboard_slug
|
||||
FROM alert
|
||||
INNER JOIN dashboard on dashboard.id = alert.dashboard_id `)
|
||||
|
||||
sql.WriteString(`WHERE org_id = ?`)
|
||||
params = append(params, query.OrgId)
|
||||
builder.Write(`WHERE alert.org_id = ?`, query.OrgId)
|
||||
|
||||
if query.DashboardId != 0 {
|
||||
sql.WriteString(` AND dashboard_id = ?`)
|
||||
params = append(params, query.DashboardId)
|
||||
builder.Write(` AND alert.dashboard_id = ?`, query.DashboardId)
|
||||
}
|
||||
|
||||
if query.PanelId != 0 {
|
||||
sql.WriteString(` AND panel_id = ?`)
|
||||
params = append(params, query.PanelId)
|
||||
builder.Write(` AND alert.panel_id = ?`, query.PanelId)
|
||||
}
|
||||
|
||||
if len(query.State) > 0 && query.State[0] != "all" {
|
||||
sql.WriteString(` AND (`)
|
||||
builder.Write(` AND (`)
|
||||
for i, v := range query.State {
|
||||
if i > 0 {
|
||||
sql.WriteString(" OR ")
|
||||
builder.Write(" OR ")
|
||||
}
|
||||
if strings.HasPrefix(v, "not_") {
|
||||
sql.WriteString("state <> ? ")
|
||||
builder.Write("state <> ? ")
|
||||
v = strings.TrimPrefix(v, "not_")
|
||||
} else {
|
||||
sql.WriteString("state = ? ")
|
||||
builder.Write("state = ? ")
|
||||
}
|
||||
params = append(params, v)
|
||||
builder.AddParams(v)
|
||||
}
|
||||
sql.WriteString(")")
|
||||
builder.Write(")")
|
||||
}
|
||||
|
||||
sql.WriteString(" ORDER BY name ASC")
|
||||
if query.User.OrgRole != m.ROLE_ADMIN {
|
||||
builder.writeDashboardPermissionFilter(query.User, m.PERMISSION_EDIT)
|
||||
}
|
||||
|
||||
builder.Write(" ORDER BY name ASC")
|
||||
|
||||
if query.Limit != 0 {
|
||||
sql.WriteString(" LIMIT ?")
|
||||
params = append(params, query.Limit)
|
||||
builder.Write(" LIMIT ?", query.Limit)
|
||||
}
|
||||
|
||||
alerts := make([]*m.Alert, 0)
|
||||
if err := x.SQL(sql.String(), params...).Find(&alerts); err != nil {
|
||||
alerts := make([]*m.AlertListItemDTO, 0)
|
||||
if err := x.SQL(builder.GetSqlString(), builder.params...).Find(&alerts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -71,15 +71,21 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Can read properties", func() {
|
||||
alertQuery := m.GetAlertsQuery{DashboardId: testDash.Id, PanelId: 1, OrgId: 1}
|
||||
alertQuery := m.GetAlertsQuery{DashboardId: testDash.Id, PanelId: 1, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
|
||||
err2 := HandleAlertsQuery(&alertQuery)
|
||||
|
||||
alert := alertQuery.Result[0]
|
||||
So(err2, ShouldBeNil)
|
||||
So(alert.Name, ShouldEqual, "Alerting title")
|
||||
So(alert.Message, ShouldEqual, "Alerting message")
|
||||
So(alert.State, ShouldEqual, "pending")
|
||||
So(alert.Frequency, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Viewer cannot read alerts", func() {
|
||||
alertQuery := m.GetAlertsQuery{DashboardId: testDash.Id, PanelId: 1, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_VIEWER}}
|
||||
err2 := HandleAlertsQuery(&alertQuery)
|
||||
|
||||
So(err2, ShouldBeNil)
|
||||
So(alertQuery.Result, ShouldHaveLength, 0)
|
||||
})
|
||||
|
||||
Convey("Alerts with same dashboard id and panel id should update", func() {
|
||||
@@ -100,7 +106,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Alerts should be updated", func() {
|
||||
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1}
|
||||
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
|
||||
err2 := HandleAlertsQuery(&query)
|
||||
|
||||
So(err2, ShouldBeNil)
|
||||
@@ -149,7 +155,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
Convey("Should save 3 dashboards", func() {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
queryForDashboard := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1}
|
||||
queryForDashboard := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
|
||||
err2 := HandleAlertsQuery(&queryForDashboard)
|
||||
|
||||
So(err2, ShouldBeNil)
|
||||
@@ -163,7 +169,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
err = SaveAlerts(&cmd)
|
||||
|
||||
Convey("should delete the missing alert", func() {
|
||||
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1}
|
||||
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
|
||||
err2 := HandleAlertsQuery(&query)
|
||||
So(err2, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
@@ -198,7 +204,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Alerts should be removed", func() {
|
||||
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1}
|
||||
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
|
||||
err2 := HandleAlertsQuery(&query)
|
||||
|
||||
So(testDash.Id, ShouldEqual, 1)
|
||||
|
||||
@@ -21,9 +21,9 @@ func init() {
|
||||
bus.AddHandler("sql", GetDashboardSlugById)
|
||||
bus.AddHandler("sql", GetDashboardUIDById)
|
||||
bus.AddHandler("sql", GetDashboardsByPluginId)
|
||||
bus.AddHandler("sql", GetFoldersForSignedInUser)
|
||||
bus.AddHandler("sql", GetDashboardPermissionsForUser)
|
||||
bus.AddHandler("sql", GetDashboardsBySlug)
|
||||
bus.AddHandler("sql", ValidateDashboardBeforeSave)
|
||||
}
|
||||
|
||||
var generateNewUid func() string = util.GenerateShortUid
|
||||
@@ -37,40 +37,29 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
|
||||
dash := cmd.GetDashboardModel()
|
||||
|
||||
if err := getExistingDashboardForUpdate(sess, dash, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var existingByTitleAndFolder m.Dashboard
|
||||
|
||||
dashWithTitleAndFolderExists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existingByTitleAndFolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dashWithTitleAndFolderExists {
|
||||
if dash.Id != existingByTitleAndFolder.Id {
|
||||
if existingByTitleAndFolder.IsFolder && !cmd.IsFolder {
|
||||
return m.ErrDashboardWithSameNameAsFolder
|
||||
}
|
||||
|
||||
if !existingByTitleAndFolder.IsFolder && cmd.IsFolder {
|
||||
return m.ErrDashboardFolderWithSameNameAsDashboard
|
||||
}
|
||||
if dash.Id > 0 {
|
||||
var existing m.Dashboard
|
||||
dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !dashWithIdExists {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
// check for is someone else has written in between
|
||||
if dash.Version != existing.Version {
|
||||
if cmd.Overwrite {
|
||||
dash.Id = existingByTitleAndFolder.Id
|
||||
dash.Created = existingByTitleAndFolder.Created
|
||||
dash.CreatedBy = existingByTitleAndFolder.CreatedBy
|
||||
dash.Version = existingByTitleAndFolder.Version
|
||||
|
||||
if dash.Uid == "" {
|
||||
dash.Uid = existingByTitleAndFolder.Uid
|
||||
}
|
||||
dash.SetVersion(existing.Version)
|
||||
} else {
|
||||
return m.ErrDashboardWithSameNameInFolderExists
|
||||
return m.ErrDashboardVersionMismatch
|
||||
}
|
||||
}
|
||||
|
||||
// do not allow plugin dashboard updates without overwrite flag
|
||||
if existing.PluginId != "" && cmd.Overwrite == false {
|
||||
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
|
||||
}
|
||||
}
|
||||
|
||||
if dash.Uid == "" {
|
||||
@@ -78,32 +67,27 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dash.Uid = uid
|
||||
dash.Data.Set("uid", uid)
|
||||
}
|
||||
|
||||
err = setHasAcl(sess, dash)
|
||||
if err != nil {
|
||||
return err
|
||||
dash.SetUid(uid)
|
||||
}
|
||||
|
||||
parentVersion := dash.Version
|
||||
affectedRows := int64(0)
|
||||
var err error
|
||||
|
||||
if dash.Id == 0 {
|
||||
dash.Version = 1
|
||||
dash.SetVersion(1)
|
||||
metrics.M_Api_Dashboard_Insert.Inc()
|
||||
dash.Data.Set("version", dash.Version)
|
||||
affectedRows, err = sess.Insert(dash)
|
||||
} else {
|
||||
dash.Version++
|
||||
dash.Data.Set("version", dash.Version)
|
||||
v := dash.Version
|
||||
v++
|
||||
dash.SetVersion(v)
|
||||
|
||||
if !cmd.UpdatedAt.IsZero() {
|
||||
dash.Updated = cmd.UpdatedAt
|
||||
}
|
||||
|
||||
affectedRows, err = sess.MustCols("folder_id", "has_acl").ID(dash.Id).Update(dash)
|
||||
affectedRows, err = sess.MustCols("folder_id").ID(dash.Id).Update(dash)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -153,77 +137,6 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func getExistingDashboardForUpdate(sess *DBSession, dash *m.Dashboard, cmd *m.SaveDashboardCommand) (err error) {
|
||||
dashWithIdExists := false
|
||||
var existingById m.Dashboard
|
||||
|
||||
if dash.Id > 0 {
|
||||
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !dashWithIdExists {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
dash.Created = existingById.Created
|
||||
dash.CreatedBy = existingById.CreatedBy
|
||||
|
||||
if dash.Uid == "" {
|
||||
dash.Uid = existingById.Uid
|
||||
}
|
||||
}
|
||||
|
||||
dashWithUidExists := false
|
||||
var existingByUid m.Dashboard
|
||||
|
||||
if dash.Uid != "" {
|
||||
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !dashWithIdExists && !dashWithUidExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
|
||||
return m.ErrDashboardWithSameUIDExists
|
||||
}
|
||||
|
||||
existing := existingById
|
||||
|
||||
if !dashWithIdExists && dashWithUidExists {
|
||||
dash.Id = existingByUid.Id
|
||||
dash.Created = existingByUid.Created
|
||||
dash.CreatedBy = existingByUid.CreatedBy
|
||||
existing = existingByUid
|
||||
}
|
||||
|
||||
if (existing.IsFolder && !cmd.IsFolder) ||
|
||||
(!existing.IsFolder && cmd.IsFolder) {
|
||||
return m.ErrDashboardTypeMismatch
|
||||
}
|
||||
|
||||
// check for is someone else has written in between
|
||||
if dash.Version != existing.Version {
|
||||
if cmd.Overwrite {
|
||||
dash.Version = existing.Version
|
||||
} else {
|
||||
return m.ErrDashboardVersionMismatch
|
||||
}
|
||||
}
|
||||
|
||||
// do not allow plugin dashboard updates without overwrite flag
|
||||
if existing.PluginId != "" && cmd.Overwrite == false {
|
||||
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
uid := generateNewUid()
|
||||
@@ -241,31 +154,6 @@ func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
|
||||
return "", m.ErrDashboardFailedGenerateUniqueUid
|
||||
}
|
||||
|
||||
func setHasAcl(sess *DBSession, dash *m.Dashboard) error {
|
||||
// check if parent has acl
|
||||
if dash.FolderId > 0 {
|
||||
var parent m.Dashboard
|
||||
if hasParent, err := sess.Where("folder_id=?", dash.FolderId).Get(&parent); err != nil {
|
||||
return err
|
||||
} else if hasParent && parent.HasAcl {
|
||||
dash.HasAcl = true
|
||||
}
|
||||
}
|
||||
|
||||
// check if dash has its own acl
|
||||
if dash.Id > 0 {
|
||||
if res, err := sess.Query("SELECT 1 from dashboard_acl WHERE dashboard_id =?", dash.Id); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if len(res) > 0 {
|
||||
dash.HasAcl = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDashboard(query *m.GetDashboardQuery) error {
|
||||
dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid}
|
||||
has, err := x.Get(&dashboard)
|
||||
@@ -276,8 +164,8 @@ func GetDashboard(query *m.GetDashboardQuery) error {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
dashboard.Data.Set("id", dashboard.Id)
|
||||
dashboard.Data.Set("uid", dashboard.Uid)
|
||||
dashboard.SetId(dashboard.Id)
|
||||
dashboard.SetUid(dashboard.Uid)
|
||||
query.Result = &dashboard
|
||||
return nil
|
||||
}
|
||||
@@ -301,7 +189,7 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
|
||||
limit = 1000
|
||||
}
|
||||
|
||||
sb := NewSearchBuilder(query.SignedInUser, limit).
|
||||
sb := NewSearchBuilder(query.SignedInUser, limit, query.Permission).
|
||||
WithTags(query.Tags).
|
||||
WithDashboardIdsIn(query.DashboardIds)
|
||||
|
||||
@@ -402,62 +290,6 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func GetFoldersForSignedInUser(query *m.GetFoldersForSignedInUserQuery) error {
|
||||
query.Result = make([]*m.DashboardFolder, 0)
|
||||
var err error
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
if query.SignedInUser.OrgRole == m.ROLE_ADMIN {
|
||||
sql := `SELECT distinct d.id, d.title
|
||||
FROM dashboard AS d WHERE d.is_folder = ? AND d.org_id = ?`
|
||||
params = append(params, dialect.BooleanStr(true))
|
||||
params = append(params, query.OrgId)
|
||||
|
||||
if len(query.Title) > 0 {
|
||||
sql += " AND d.title " + dialect.LikeStr() + " ?"
|
||||
params = append(params, "%"+query.Title+"%")
|
||||
}
|
||||
|
||||
sql += ` ORDER BY d.title ASC`
|
||||
|
||||
err = x.Sql(sql, params...).Find(&query.Result)
|
||||
} else {
|
||||
sql := `SELECT distinct d.id, d.title
|
||||
FROM dashboard AS d
|
||||
LEFT JOIN dashboard_acl AS da ON d.id = da.dashboard_id
|
||||
LEFT JOIN team_member AS ugm ON ugm.team_id = da.team_id
|
||||
LEFT JOIN org_user ou ON ou.role = da.role AND ou.user_id = ?
|
||||
LEFT JOIN org_user ouRole ON ouRole.role = 'Editor' AND ouRole.user_id = ? AND ouRole.org_id = ?`
|
||||
params = append(params, query.SignedInUser.UserId)
|
||||
params = append(params, query.SignedInUser.UserId)
|
||||
params = append(params, query.OrgId)
|
||||
|
||||
sql += ` WHERE
|
||||
d.org_id = ? AND
|
||||
d.is_folder = ? AND
|
||||
(
|
||||
(d.has_acl = ? AND da.permission > 1 AND (da.user_id = ? OR ugm.user_id = ? OR ou.id IS NOT NULL))
|
||||
OR (d.has_acl = ? AND ouRole.id IS NOT NULL)
|
||||
)`
|
||||
params = append(params, query.OrgId)
|
||||
params = append(params, dialect.BooleanStr(true))
|
||||
params = append(params, dialect.BooleanStr(true))
|
||||
params = append(params, query.SignedInUser.UserId)
|
||||
params = append(params, query.SignedInUser.UserId)
|
||||
params = append(params, dialect.BooleanStr(false))
|
||||
|
||||
if len(query.Title) > 0 {
|
||||
sql += " AND d.title " + dialect.LikeStr() + " ?"
|
||||
params = append(params, "%"+query.Title+"%")
|
||||
}
|
||||
|
||||
sql += ` ORDER BY d.title ASC`
|
||||
err = x.Sql(sql, params...).Find(&query.Result)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
dashboard := m.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
|
||||
@@ -642,3 +474,128 @@ func GetDashboardUIDById(query *m.GetDashboardRefByIdQuery) error {
|
||||
query.Result = us
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
|
||||
dash := cmd.Dashboard
|
||||
|
||||
dashWithIdExists := false
|
||||
var existingById m.Dashboard
|
||||
|
||||
if dash.Id > 0 {
|
||||
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !dashWithIdExists {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
if dash.Uid == "" {
|
||||
dash.SetUid(existingById.Uid)
|
||||
}
|
||||
}
|
||||
|
||||
dashWithUidExists := false
|
||||
var existingByUid m.Dashboard
|
||||
|
||||
if dash.Uid != "" {
|
||||
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dash.FolderId > 0 {
|
||||
var existingFolder m.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
|
||||
}
|
||||
|
||||
if !folderExists {
|
||||
return m.ErrDashboardFolderNotFound
|
||||
}
|
||||
}
|
||||
|
||||
if !dashWithIdExists && !dashWithUidExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
|
||||
return m.ErrDashboardWithSameUIDExists
|
||||
}
|
||||
|
||||
existing := existingById
|
||||
|
||||
if !dashWithIdExists && dashWithUidExists {
|
||||
dash.SetId(existingByUid.Id)
|
||||
dash.SetUid(existingByUid.Uid)
|
||||
existing = existingByUid
|
||||
}
|
||||
|
||||
if (existing.IsFolder && !dash.IsFolder) ||
|
||||
(!existing.IsFolder && dash.IsFolder) {
|
||||
return m.ErrDashboardTypeMismatch
|
||||
}
|
||||
|
||||
// check for is someone else has written in between
|
||||
if dash.Version != existing.Version {
|
||||
if cmd.Overwrite {
|
||||
dash.SetVersion(existing.Version)
|
||||
} else {
|
||||
return m.ErrDashboardVersionMismatch
|
||||
}
|
||||
}
|
||||
|
||||
// do not allow plugin dashboard updates without overwrite flag
|
||||
if existing.PluginId != "" && cmd.Overwrite == false {
|
||||
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) error {
|
||||
dash := cmd.Dashboard
|
||||
var existing m.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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists && dash.Id != existing.Id {
|
||||
if existing.IsFolder && !dash.IsFolder {
|
||||
return m.ErrDashboardWithSameNameAsFolder
|
||||
}
|
||||
|
||||
if !existing.IsFolder && dash.IsFolder {
|
||||
return m.ErrDashboardFolderWithSameNameAsDashboard
|
||||
}
|
||||
|
||||
if cmd.Overwrite {
|
||||
dash.SetId(existing.Id)
|
||||
dash.SetUid(existing.Uid)
|
||||
dash.SetVersion(existing.Version)
|
||||
} else {
|
||||
return m.ErrDashboardWithSameNameInFolderExists
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateDashboardBeforeSave(cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = getExistingDashboardByTitleAndFolder(sess, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", SetDashboardAcl)
|
||||
bus.AddHandler("sql", UpdateDashboardAcl)
|
||||
bus.AddHandler("sql", RemoveDashboardAcl)
|
||||
bus.AddHandler("sql", GetDashboardAclInfoList)
|
||||
}
|
||||
|
||||
@@ -24,7 +19,7 @@ func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error {
|
||||
}
|
||||
|
||||
for _, item := range cmd.Items {
|
||||
if item.UserId == 0 && item.TeamId == 0 && !item.Role.IsValid() {
|
||||
if item.UserId == 0 && item.TeamId == 0 && (item.Role == nil || !item.Role.IsValid()) {
|
||||
return m.ErrDashboardAclInfoMissing
|
||||
}
|
||||
|
||||
@@ -40,92 +35,13 @@ func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error {
|
||||
|
||||
// Update dashboard HasAcl flag
|
||||
dashboard := m.Dashboard{HasAcl: true}
|
||||
if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
if _, err := sess.Cols("has_acl").Where("id=?", cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if cmd.UserId == 0 && cmd.TeamId == 0 {
|
||||
return m.ErrDashboardAclInfoMissing
|
||||
}
|
||||
|
||||
if cmd.DashboardId == 0 {
|
||||
return m.ErrDashboardPermissionDashboardEmpty
|
||||
}
|
||||
|
||||
if res, err := sess.Query("SELECT 1 from "+dialect.Quote("dashboard_acl")+" WHERE dashboard_id =? and (team_id=? or user_id=?)", cmd.DashboardId, cmd.TeamId, cmd.UserId); err != nil {
|
||||
return err
|
||||
} else if len(res) == 1 {
|
||||
|
||||
entity := m.DashboardAcl{
|
||||
Permission: cmd.Permission,
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
if _, err := sess.Cols("updated", "permission").Where("dashboard_id =? and (team_id=? or user_id=?)", cmd.DashboardId, cmd.TeamId, cmd.UserId).Update(&entity); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
entity := m.DashboardAcl{
|
||||
OrgId: cmd.OrgId,
|
||||
TeamId: cmd.TeamId,
|
||||
UserId: cmd.UserId,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
DashboardId: cmd.DashboardId,
|
||||
Permission: cmd.Permission,
|
||||
}
|
||||
|
||||
cols := []string{"org_id", "created", "updated", "dashboard_id", "permission"}
|
||||
|
||||
if cmd.UserId != 0 {
|
||||
cols = append(cols, "user_id")
|
||||
}
|
||||
|
||||
if cmd.TeamId != 0 {
|
||||
cols = append(cols, "team_id")
|
||||
}
|
||||
|
||||
_, err := sess.Cols(cols...).Insert(&entity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = entity
|
||||
|
||||
// Update dashboard HasAcl flag
|
||||
dashboard := m.Dashboard{
|
||||
HasAcl: true,
|
||||
}
|
||||
|
||||
if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveDashboardAcl removes a specified permission from the dashboard acl
|
||||
func RemoveDashboardAcl(cmd *m.RemoveDashboardAclCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
var rawSQL = "DELETE FROM " + dialect.Quote("dashboard_acl") + " WHERE org_id =? and id=?"
|
||||
_, err := sess.Exec(rawSQL, cmd.OrgId, cmd.AclId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// GetDashboardAclInfoList returns a list of permissions for a dashboard. They can be fetched from three
|
||||
// different places.
|
||||
// 1) Permissions for the dashboard
|
||||
@@ -134,6 +50,8 @@ func RemoveDashboardAcl(cmd *m.RemoveDashboardAclCommand) error {
|
||||
func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
var err error
|
||||
|
||||
falseStr := dialect.BooleanStr(false)
|
||||
|
||||
if query.DashboardId == 0 {
|
||||
sql := `SELECT
|
||||
da.id,
|
||||
@@ -151,18 +69,13 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
'' as title,
|
||||
'' as slug,
|
||||
'' as uid,` +
|
||||
dialect.BooleanStr(false) + ` AS is_folder
|
||||
falseStr + ` AS is_folder
|
||||
FROM dashboard_acl as da
|
||||
WHERE da.dashboard_id = -1`
|
||||
query.Result = make([]*m.DashboardAclInfoDTO, 0)
|
||||
err = x.SQL(sql).Find(&query.Result)
|
||||
|
||||
} else {
|
||||
dashboardFilter := fmt.Sprintf(`IN (
|
||||
SELECT %d
|
||||
UNION
|
||||
SELECT folder_id from dashboard where id = %d
|
||||
)`, query.DashboardId, query.DashboardId)
|
||||
|
||||
rawSQL := `
|
||||
-- get permissions for the dashboard and its parent folder
|
||||
@@ -183,41 +96,21 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
d.slug,
|
||||
d.uid,
|
||||
d.is_folder
|
||||
FROM` + dialect.Quote("dashboard_acl") + ` as da
|
||||
LEFT OUTER JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
|
||||
LEFT OUTER JOIN team ug on ug.id = da.team_id
|
||||
LEFT OUTER JOIN dashboard d on da.dashboard_id = d.id
|
||||
WHERE dashboard_id ` + dashboardFilter + ` AND da.org_id = ?
|
||||
|
||||
-- Also include default permissions if folder or dashboard field "has_acl" is false
|
||||
|
||||
UNION
|
||||
SELECT
|
||||
da.id,
|
||||
da.org_id,
|
||||
da.dashboard_id,
|
||||
da.user_id,
|
||||
da.team_id,
|
||||
da.permission,
|
||||
da.role,
|
||||
da.created,
|
||||
da.updated,
|
||||
'' as user_login,
|
||||
'' as user_email,
|
||||
'' as team,
|
||||
folder.title,
|
||||
folder.slug,
|
||||
folder.uid,
|
||||
folder.is_folder
|
||||
FROM dashboard_acl as da,
|
||||
dashboard as dash
|
||||
LEFT OUTER JOIN dashboard folder on dash.folder_id = folder.id
|
||||
WHERE
|
||||
dash.id = ? AND (
|
||||
dash.has_acl = ` + dialect.BooleanStr(false) + ` or
|
||||
folder.has_acl = ` + dialect.BooleanStr(false) + `
|
||||
) AND
|
||||
da.dashboard_id = -1
|
||||
FROM dashboard as d
|
||||
LEFT JOIN dashboard folder on folder.id = d.folder_id
|
||||
LEFT JOIN dashboard_acl AS da ON
|
||||
da.dashboard_id = d.id OR
|
||||
da.dashboard_id = d.folder_id OR
|
||||
(
|
||||
-- include default permissions -->
|
||||
da.org_id = -1 AND (
|
||||
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
|
||||
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
|
||||
)
|
||||
)
|
||||
LEFT JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
|
||||
LEFT JOIN team ug on ug.id = da.team_id
|
||||
WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
|
||||
ORDER BY 1 ASC
|
||||
`
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
childDash := insertTestDashboard("2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
|
||||
Convey("When adding dashboard permission with userId and teamId set to 0", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
|
||||
OrgId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
@@ -41,8 +41,25 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given dashboard folder with removed default permissions", func() {
|
||||
err := UpdateDashboardAcl(&m.UpdateDashboardAclCommand{
|
||||
DashboardId: savedFolder.Id,
|
||||
Items: []*m.DashboardAcl{},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When reading dashboard acl should return no acl items", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1}
|
||||
|
||||
err := GetDashboardAclInfoList(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given dashboard folder permission", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
@@ -61,7 +78,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Given child dashboard permission", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
err := testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: childDash.Id,
|
||||
@@ -83,7 +100,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Given child dashboard permission in folder with no permissions", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
err := testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: childDash.Id,
|
||||
@@ -108,17 +125,12 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to add dashboard permission", func() {
|
||||
setDashAclCmd := m.SetDashboardAclCommand{
|
||||
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
}
|
||||
|
||||
err := SetDashboardAcl(&setDashAclCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(setDashAclCmd.Result.Id, ShouldEqual, 3)
|
||||
})
|
||||
|
||||
q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q1)
|
||||
@@ -130,42 +142,9 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(q1.Result[0].UserId, ShouldEqual, currentUser.Id)
|
||||
So(q1.Result[0].UserLogin, ShouldEqual, currentUser.Login)
|
||||
So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email)
|
||||
So(q1.Result[0].Id, ShouldEqual, setDashAclCmd.Result.Id)
|
||||
|
||||
Convey("Should update hasAcl field to true for dashboard folder and its children", func() {
|
||||
q2 := &m.GetDashboardsQuery{DashboardIds: []int64{savedFolder.Id, childDash.Id}}
|
||||
err := GetDashboards(q2)
|
||||
So(err, ShouldBeNil)
|
||||
So(q2.Result[0].HasAcl, ShouldBeTrue)
|
||||
So(q2.Result[1].HasAcl, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Should be able to update an existing permission", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_ADMIN,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q3)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 1)
|
||||
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
|
||||
So(q3.Result[0].UserId, ShouldEqual, 1)
|
||||
|
||||
})
|
||||
|
||||
Convey("Should be able to delete an existing permission", func() {
|
||||
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
AclId: setDashAclCmd.Result.Id,
|
||||
})
|
||||
|
||||
err := testHelperUpdateDashboardAcl(savedFolder.Id)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
@@ -181,14 +160,12 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should be able to add a user permission for a team", func() {
|
||||
setDashAclCmd := m.SetDashboardAclCommand{
|
||||
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
|
||||
OrgId: 1,
|
||||
TeamId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
}
|
||||
|
||||
err := SetDashboardAcl(&setDashAclCmd)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
@@ -197,23 +174,10 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].TeamId, ShouldEqual, group1.Result.Id)
|
||||
|
||||
Convey("Should be able to delete an existing permission for a team", func() {
|
||||
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
AclId: setDashAclCmd.Result.Id,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q3)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should be able to update an existing permission for a team", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
|
||||
OrgId: 1,
|
||||
TeamId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
@@ -229,7 +193,6 @@ func TestDashboardAclDataAccess(t *testing.T) {
|
||||
So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
|
||||
So(q3.Result[0].TeamId, ShouldEqual, group1.Result.Id)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -26,7 +26,11 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
|
||||
Convey("and no acls are set", func() {
|
||||
Convey("should return all dashboards", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}}
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
@@ -37,10 +41,13 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
|
||||
Convey("and acl is set for dashboard folder", func() {
|
||||
var otherUser int64 = 999
|
||||
updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT)
|
||||
testHelperUpdateDashboardAcl(folder.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
|
||||
|
||||
Convey("should not return folder", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}}
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
|
||||
OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
@@ -48,10 +55,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("when the user is given permission", func() {
|
||||
updateTestDashboardWithAcl(folder.Id, currentUser.Id, m.PERMISSION_EDIT)
|
||||
testHelperUpdateDashboardAcl(folder.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: currentUser.Id, Permission: m.PERMISSION_EDIT})
|
||||
|
||||
Convey("should be able to access folder", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}}
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
@@ -82,12 +93,11 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
|
||||
Convey("and acl is set for dashboard child and folder has all permissions removed", func() {
|
||||
var otherUser int64 = 999
|
||||
aclId := updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT)
|
||||
removeAcl(aclId)
|
||||
updateTestDashboardWithAcl(childDash.Id, otherUser, m.PERMISSION_EDIT)
|
||||
testHelperUpdateDashboardAcl(folder.Id)
|
||||
testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
|
||||
|
||||
Convey("should not return folder or child", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
|
||||
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
@@ -95,10 +105,10 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("when the user is given permission to child", func() {
|
||||
updateTestDashboardWithAcl(childDash.Id, currentUser.Id, m.PERMISSION_EDIT)
|
||||
testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{DashboardId: childDash.Id, OrgId: 1, UserId: currentUser.Id, Permission: m.PERMISSION_EDIT})
|
||||
|
||||
Convey("should be able to search for child dashboard but not folder", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
|
||||
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
@@ -141,7 +151,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
|
||||
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: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1}
|
||||
query := &search.FindPersistedDashboardsQuery{FolderIds: []int64{rootFolderId, folder1.Id}, SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 4)
|
||||
@@ -154,15 +164,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
|
||||
Convey("and acl is set for one dashboard folder", func() {
|
||||
var otherUser int64 = 999
|
||||
updateTestDashboardWithAcl(folder1.Id, otherUser, m.PERMISSION_EDIT)
|
||||
testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
|
||||
|
||||
Convey("and a dashboard is moved from folder without acl to the folder with an acl", func() {
|
||||
movedDash := moveDashboard(1, childDash2.Data, folder1.Id)
|
||||
So(movedDash.HasAcl, ShouldBeTrue)
|
||||
moveDashboard(1, childDash2.Data, folder1.Id)
|
||||
|
||||
Convey("should not return folder with acl or its children", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder1.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
|
||||
}
|
||||
@@ -172,14 +181,12 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and a dashboard is moved from folder with acl to the folder without an acl", func() {
|
||||
movedDash := moveDashboard(1, childDash1.Data, folder2.Id)
|
||||
So(movedDash.HasAcl, ShouldBeFalse)
|
||||
moveDashboard(1, childDash1.Data, folder2.Id)
|
||||
|
||||
Convey("should return folder without acl and its children", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
|
||||
}
|
||||
@@ -194,22 +201,22 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("and a dashboard with an acl is moved to the folder without an acl", func() {
|
||||
updateTestDashboardWithAcl(childDash1.Id, otherUser, m.PERMISSION_EDIT)
|
||||
movedDash := moveDashboard(1, childDash1.Data, folder2.Id)
|
||||
So(movedDash.HasAcl, ShouldBeTrue)
|
||||
testHelperUpdateDashboardAcl(childDash1.Id, m.DashboardAcl{DashboardId: childDash1.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
|
||||
moveDashboard(1, childDash1.Data, folder2.Id)
|
||||
|
||||
Convey("should return folder without acl but not the dashboard with acl", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
So(len(query.Result), ShouldEqual, 4)
|
||||
So(query.Result[0].Id, ShouldEqual, folder2.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, childDash2.Id)
|
||||
So(query.Result[2].Id, ShouldEqual, dashInRoot.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, childDash1.Id)
|
||||
So(query.Result[2].Id, ShouldEqual, childDash2.Id)
|
||||
So(query.Result[3].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -227,12 +234,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
|
||||
Convey("Admin users", func() {
|
||||
Convey("Should have write access to all dashboard folders in their org", func() {
|
||||
query := m.GetFoldersForSignedInUserQuery{
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
SignedInUser: &m.SignedInUser{UserId: adminUser.Id, OrgRole: m.ROLE_ADMIN},
|
||||
SignedInUser: &m.SignedInUser{UserId: adminUser.Id, OrgRole: m.ROLE_ADMIN, OrgId: 1},
|
||||
Permission: m.PERMISSION_VIEW,
|
||||
Type: "dash-folder",
|
||||
}
|
||||
|
||||
err := GetFoldersForSignedInUser(&query)
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
@@ -260,13 +269,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Editor users", func() {
|
||||
query := m.GetFoldersForSignedInUserQuery{
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
SignedInUser: &m.SignedInUser{UserId: editorUser.Id, OrgRole: m.ROLE_EDITOR},
|
||||
SignedInUser: &m.SignedInUser{UserId: editorUser.Id, OrgRole: m.ROLE_EDITOR, OrgId: 1},
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
}
|
||||
|
||||
Convey("Should have write access to all dashboard folders with default ACL", func() {
|
||||
err := GetFoldersForSignedInUser(&query)
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
@@ -293,9 +303,9 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() {
|
||||
updateTestDashboardWithAcl(folder1.Id, editorUser.Id, m.PERMISSION_VIEW)
|
||||
testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: editorUser.Id, Permission: m.PERMISSION_VIEW})
|
||||
|
||||
err := GetFoldersForSignedInUser(&query)
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
@@ -305,13 +315,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Viewer users", func() {
|
||||
query := m.GetFoldersForSignedInUserQuery{
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgRole: m.ROLE_VIEWER},
|
||||
SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgRole: m.ROLE_VIEWER, OrgId: 1},
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
}
|
||||
|
||||
Convey("Should have no write access to any dashboard folders with default ACL", func() {
|
||||
err := GetFoldersForSignedInUser(&query)
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 0)
|
||||
@@ -336,9 +347,9 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() {
|
||||
updateTestDashboardWithAcl(folder1.Id, viewerUser.Id, m.PERMISSION_EDIT)
|
||||
testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: m.PERMISSION_EDIT})
|
||||
|
||||
err := GetFoldersForSignedInUser(&query)
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
|
||||
@@ -26,8 +26,8 @@ func SaveProvisionedDashboard(cmd *models.SaveProvisionedDashboardCommand) error
|
||||
}
|
||||
|
||||
cmd.Result = cmd.DashboardCmd.Result
|
||||
if cmd.DashboardProvisioning.Updated.IsZero() {
|
||||
cmd.DashboardProvisioning.Updated = cmd.Result.Updated
|
||||
if cmd.DashboardProvisioning.Updated == 0 {
|
||||
cmd.DashboardProvisioning.Updated = cmd.Result.Updated.Unix()
|
||||
}
|
||||
|
||||
return saveProvionedData(sess, cmd.DashboardProvisioning, cmd.Result)
|
||||
|
||||
@@ -2,6 +2,7 @@ package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -23,11 +24,14 @@ func TestDashboardProvisioningTest(t *testing.T) {
|
||||
}
|
||||
|
||||
Convey("Saving dashboards with extras", func() {
|
||||
now := time.Now()
|
||||
|
||||
cmd := &models.SaveProvisionedDashboardCommand{
|
||||
DashboardCmd: saveDashboardCmd,
|
||||
DashboardProvisioning: &models.DashboardProvisioning{
|
||||
Name: "default",
|
||||
ExternalId: "/var/grafana.json",
|
||||
Updated: now.Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -44,6 +48,7 @@ func TestDashboardProvisioningTest(t *testing.T) {
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
So(query.Result[0].DashboardId, ShouldEqual, dashId)
|
||||
So(query.Result[0].Updated, ShouldEqual, now.Unix())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
984
pkg/services/sqlstore/dashboard_service_integration_test.go
Normal file
984
pkg/services/sqlstore/dashboard_service_integration_test.go
Normal file
@@ -0,0 +1,984 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestIntegratedDashboardService(t *testing.T) {
|
||||
Convey("Dashboard service integration tests", t, func() {
|
||||
InitTestDB(t)
|
||||
var testOrgId int64 = 1
|
||||
|
||||
Convey("Given saved folders and dashboards in organization A", func() {
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
savedFolder := saveTestFolder("Saved folder", testOrgId)
|
||||
savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
|
||||
saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
|
||||
savedDashInGeneralFolder := saveTestDashboard("Saved dashboard in general folder", testOrgId, 0)
|
||||
otherSavedFolder := saveTestFolder("Other saved folder", testOrgId)
|
||||
|
||||
Convey("Should return dashboard model", func() {
|
||||
So(savedFolder.Title, ShouldEqual, "Saved folder")
|
||||
So(savedFolder.Slug, ShouldEqual, "saved-folder")
|
||||
So(savedFolder.Id, ShouldNotEqual, 0)
|
||||
So(savedFolder.IsFolder, ShouldBeTrue)
|
||||
So(savedFolder.FolderId, ShouldEqual, 0)
|
||||
So(len(savedFolder.Uid), ShouldBeGreaterThan, 0)
|
||||
|
||||
So(savedDashInFolder.Title, ShouldEqual, "Saved dash in folder")
|
||||
So(savedDashInFolder.Slug, ShouldEqual, "saved-dash-in-folder")
|
||||
So(savedDashInFolder.Id, ShouldNotEqual, 0)
|
||||
So(savedDashInFolder.IsFolder, ShouldBeFalse)
|
||||
So(savedDashInFolder.FolderId, ShouldEqual, savedFolder.Id)
|
||||
So(len(savedDashInFolder.Uid), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
|
||||
// Basic validation tests
|
||||
|
||||
Convey("When saving a dashboard with non-existing id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": float64(123412321),
|
||||
"title": "Expect error",
|
||||
}),
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in not found error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardNotFound)
|
||||
})
|
||||
})
|
||||
|
||||
// Given other organization
|
||||
|
||||
Convey("Given organization B", func() {
|
||||
var otherOrgId int64 = 2
|
||||
|
||||
Convey("When saving a dashboard with id that are saved in organization A", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: otherOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"title": "Expect error",
|
||||
}),
|
||||
Overwrite: false,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in not found error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardNotFound)
|
||||
})
|
||||
})
|
||||
|
||||
permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
|
||||
Convey("When saving a dashboard with uid that are saved in organization A", func() {
|
||||
var otherOrgId int64 = 2
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: otherOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "Dash with existing uid in other org",
|
||||
}),
|
||||
Overwrite: false,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
|
||||
Convey("It should create dashboard in other organization", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: otherOrgId, Uid: savedDashInFolder.Uid}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldNotEqual, savedDashInFolder.Id)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.OrgId, ShouldEqual, otherOrgId)
|
||||
So(query.Result.Uid, ShouldEqual, savedDashInFolder.Uid)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Given user has no permission to save
|
||||
|
||||
permissionScenario("Given user has no permission to save", false, func(sc *dashboardPermissionScenarioContext) {
|
||||
|
||||
Convey("When trying to create a new dashboard in the General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.dashId, ShouldEqual, 0)
|
||||
So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to create a new dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: otherSavedFolder.Id,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and rsult in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.dashId, ShouldEqual, otherSavedFolder.Id)
|
||||
So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update a dashboard by existing id in the General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInGeneralFolder.Id,
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: savedDashInGeneralFolder.FolderId,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.dashId, ShouldEqual, savedDashInGeneralFolder.Id)
|
||||
So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update a dashboard by existing id in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: savedDashInFolder.FolderId,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.dashId, ShouldEqual, savedDashInFolder.Id)
|
||||
So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Given user has permission to save
|
||||
|
||||
permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
|
||||
|
||||
Convey("and overwrite flag is set to false", func() {
|
||||
shouldOverwrite := false
|
||||
|
||||
Convey("When creating a dashboard in General folder with same name as dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard in other folder with same name as dashboard in General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInGeneralFolder.Title,
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a folder with same name as dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new folder", func() {
|
||||
So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
|
||||
So(res.IsFolder, ShouldBeTrue)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
So(query.Result.IsFolder, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When saving a dashboard without id and uid and unique title in folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash without id and uid",
|
||||
}),
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
So(res.Id, ShouldBeGreaterThan, 0)
|
||||
So(len(res.Uid), ShouldBeGreaterThan, 0)
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.Uid, ShouldEqual, res.Uid)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When saving a dashboard when dashboard id is zero ", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": 0,
|
||||
"title": "Dash with zero id",
|
||||
}),
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should create a new dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When saving a dashboard in non-existing folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Expect error",
|
||||
}),
|
||||
FolderId: 123412321,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in folder not found error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardFolderNotFound)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by id without current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInGeneralFolder.Id,
|
||||
"title": "test dash 23",
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in version mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by id with current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInGeneralFolder.Id,
|
||||
"title": "Updated title",
|
||||
"version": savedDashInGeneralFolder.Version,
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Title, ShouldEqual, "Updated title")
|
||||
So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by uid without current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "test dash 23",
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in version mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by uid with current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "Updated title",
|
||||
"version": savedDashInFolder.Version,
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Title, ShouldEqual, "Updated title")
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard with same name as dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
FolderId: savedDashInFolder.FolderId,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in dashboard with same name in folder error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard with same name as dashboard in General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInGeneralFolder.Title,
|
||||
}),
|
||||
FolderId: savedDashInGeneralFolder.FolderId,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in dashboard with same name in folder error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a folder with same name as existing folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedFolder.Title,
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in dashboard with same name in folder error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and overwrite flag is set to true", func() {
|
||||
shouldOverwrite := true
|
||||
|
||||
Convey("When updating an existing dashboard by id without current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInGeneralFolder.Id,
|
||||
"title": "Updated title",
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Title, ShouldEqual, "Updated title")
|
||||
So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating an existing dashboard by uid without current version", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "Updated title",
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Title, ShouldEqual, "Updated title")
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating uid for existing dashboard using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"uid": "new-uid",
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
|
||||
Convey("It should update dashboard", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
So(res.Id, ShouldEqual, savedDashInFolder.Id)
|
||||
So(res.Uid, ShouldEqual, "new-uid")
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Uid, ShouldEqual, "new-uid")
|
||||
So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating uid to an existing uid for existing dashboard using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"uid": savedDashInGeneralFolder.Uid,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in same uid exists error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameUIDExists)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard with same name as dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
FolderId: savedDashInFolder.FolderId,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
|
||||
Convey("It should overwrite existing dashboard", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
So(res.Id, ShouldEqual, savedDashInFolder.Id)
|
||||
So(res.Uid, ShouldEqual, savedDashInFolder.Uid)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.Uid, ShouldEqual, res.Uid)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a dashboard with same name as dashboard in General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": savedDashInGeneralFolder.Title,
|
||||
}),
|
||||
FolderId: savedDashInGeneralFolder.FolderId,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
|
||||
Convey("It should overwrite existing dashboard", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
So(res.Id, ShouldEqual, savedDashInGeneralFolder.Id)
|
||||
So(res.Uid, ShouldEqual, savedDashInGeneralFolder.Uid)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Id, ShouldEqual, res.Id)
|
||||
So(query.Result.Uid, ShouldEqual, res.Uid)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing folder to a dashboard using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedFolder.Id,
|
||||
"title": "new title",
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in type mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing dashboard to a folder using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"title": "new folder title",
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in type mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing folder to a dashboard using uid", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedFolder.Uid,
|
||||
"title": "new title",
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in type mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing dashboard to a folder using uid", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "new folder title",
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in type mismatch error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing folder to a dashboard using title", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": savedFolder.Title,
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in dashboard with same name as folder error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardWithSameNameAsFolder)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing dashboard to a folder using title", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": savedDashInGeneralFolder.Title,
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should result in folder with same name as dashboard error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardFolderWithSameNameAsDashboard)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func mockDashboardGuardian(mock *mockDashboardGuarder) {
|
||||
guardian.New = func(dashId int64, orgId int64, user *models.SignedInUser) guardian.DashboardGuardian {
|
||||
mock.orgId = orgId
|
||||
mock.dashId = dashId
|
||||
mock.user = user
|
||||
return mock
|
||||
}
|
||||
}
|
||||
|
||||
type mockDashboardGuarder struct {
|
||||
dashId int64
|
||||
orgId int64
|
||||
user *models.SignedInUser
|
||||
canSave bool
|
||||
canSaveCallCounter int
|
||||
canEdit bool
|
||||
canView bool
|
||||
canAdmin bool
|
||||
hasPermission bool
|
||||
checkPermissionBeforeRemove bool
|
||||
checkPermissionBeforeUpdate bool
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CanSave() (bool, error) {
|
||||
g.canSaveCallCounter++
|
||||
return g.canSave, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CanEdit() (bool, error) {
|
||||
return g.canEdit, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CanView() (bool, error) {
|
||||
return g.canView, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CanAdmin() (bool, error) {
|
||||
return g.canAdmin, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) HasPermission(permission models.PermissionType) (bool, error) {
|
||||
return g.hasPermission, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) CheckPermissionBeforeUpdate(permission models.PermissionType, updatePermissions []*models.DashboardAcl) (bool, error) {
|
||||
return g.checkPermissionBeforeUpdate, nil
|
||||
}
|
||||
|
||||
func (g *mockDashboardGuarder) GetAcl() ([]*models.DashboardAclInfoDTO, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type scenarioContext struct {
|
||||
dashboardGuardianMock *mockDashboardGuarder
|
||||
}
|
||||
|
||||
type scenarioFunc func(c *scenarioContext)
|
||||
|
||||
func dashboardGuardianScenario(desc string, mock *mockDashboardGuarder, fn scenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origNewDashboardGuardian := guardian.New
|
||||
mockDashboardGuardian(mock)
|
||||
|
||||
sc := &scenarioContext{
|
||||
dashboardGuardianMock: mock,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
guardian.New = origNewDashboardGuardian
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
type dashboardPermissionScenarioContext struct {
|
||||
dashboardGuardianMock *mockDashboardGuarder
|
||||
}
|
||||
|
||||
type dashboardPermissionScenarioFunc func(sc *dashboardPermissionScenarioContext)
|
||||
|
||||
func dashboardPermissionScenario(desc string, mock *mockDashboardGuarder, fn dashboardPermissionScenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origNewDashboardGuardian := guardian.New
|
||||
mockDashboardGuardian(mock)
|
||||
|
||||
sc := &dashboardPermissionScenarioContext{
|
||||
dashboardGuardianMock: mock,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
guardian.New = origNewDashboardGuardian
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenarioFunc) {
|
||||
mock := &mockDashboardGuarder{
|
||||
canSave: canSave,
|
||||
}
|
||||
dashboardPermissionScenario(desc, mock, fn)
|
||||
}
|
||||
|
||||
func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
|
||||
dto := toSaveDashboardDto(cmd)
|
||||
res, _ := dashboards.NewService().SaveDashboard(&dto)
|
||||
return res
|
||||
}
|
||||
|
||||
func callSaveWithError(cmd models.SaveDashboardCommand) error {
|
||||
dto := toSaveDashboardDto(cmd)
|
||||
_, err := dashboards.NewService().SaveDashboard(&dto)
|
||||
return err
|
||||
}
|
||||
|
||||
func dashboardServiceScenario(desc string, mock *mockDashboardGuarder, fn scenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origNewDashboardGuardian := guardian.New
|
||||
mockDashboardGuardian(mock)
|
||||
|
||||
sc := &scenarioContext{
|
||||
dashboardGuardianMock: mock,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
guardian.New = origNewDashboardGuardian
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
IsFolder: false,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
}),
|
||||
}
|
||||
|
||||
dto := dashboards.SaveDashboardDTO{
|
||||
OrgId: orgId,
|
||||
Dashboard: cmd.GetDashboardModel(),
|
||||
User: &models.SignedInUser{
|
||||
UserId: 1,
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := dashboards.NewService().SaveDashboard(&dto)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func saveTestFolder(title string, orgId int64) *models.Dashboard {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: 0,
|
||||
IsFolder: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
}),
|
||||
}
|
||||
|
||||
dto := dashboards.SaveDashboardDTO{
|
||||
OrgId: orgId,
|
||||
Dashboard: cmd.GetDashboardModel(),
|
||||
User: &models.SignedInUser{
|
||||
UserId: 1,
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := dashboards.NewService().SaveDashboard(&dto)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO {
|
||||
dash := (&cmd).GetDashboardModel()
|
||||
|
||||
return dashboards.SaveDashboardDTO{
|
||||
Dashboard: dash,
|
||||
Message: cmd.Message,
|
||||
OrgId: cmd.OrgId,
|
||||
User: &models.SignedInUser{UserId: cmd.UserId},
|
||||
Overwrite: cmd.Overwrite,
|
||||
}
|
||||
}
|
||||
@@ -100,324 +100,6 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should return not found error if no dashboard is found for update", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Overwrite: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": float64(123412321),
|
||||
"title": "Expect error",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardNotFound)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite dashboard in another org", func() {
|
||||
query := m.GetDashboardQuery{Slug: "test-dash-23", OrgId: 1}
|
||||
GetDashboard(&query)
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 2,
|
||||
Overwrite: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": float64(query.Result.Id),
|
||||
"title": "Expect error",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardNotFound)
|
||||
})
|
||||
|
||||
Convey("Should be able to save dashboards with same name in different folders", func() {
|
||||
firstSaveCmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": "test dash folder and title",
|
||||
"tags": []interface{}{},
|
||||
"uid": "randomHash",
|
||||
}),
|
||||
FolderId: 3,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&firstSaveCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
secondSaveCmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": "test dash folder and title",
|
||||
"tags": []interface{}{},
|
||||
"uid": "moreRandomHash",
|
||||
}),
|
||||
FolderId: 1,
|
||||
}
|
||||
|
||||
err = SaveDashboard(&secondSaveCmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(firstSaveCmd.Result.Id, ShouldNotEqual, secondSaveCmd.Result.Id)
|
||||
})
|
||||
|
||||
Convey("Should be able to overwrite dashboard in same folder using title", func() {
|
||||
insertTestDashboard("Dash", 1, 0, false, "prod", "webapp")
|
||||
folder := insertTestDashboard("Folder", 1, 0, true, "prod", "webapp")
|
||||
dashInFolder := insertTestDashboard("Dash", 1, folder.Id, false, "prod", "webapp")
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: folder.Id,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.Result.Id, ShouldEqual, dashInFolder.Id)
|
||||
So(cmd.Result.Uid, ShouldEqual, dashInFolder.Uid)
|
||||
})
|
||||
|
||||
Convey("Should be able to overwrite dashboard in General folder using title", func() {
|
||||
dashInGeneral := insertTestDashboard("Dash", 1, 0, false, "prod", "webapp")
|
||||
folder := insertTestDashboard("Folder", 1, 0, true, "prod", "webapp")
|
||||
insertTestDashboard("Dash", 1, folder.Id, false, "prod", "webapp")
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.Result.Id, ShouldEqual, dashInGeneral.Id)
|
||||
So(cmd.Result.Uid, ShouldEqual, dashInGeneral.Uid)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite folder with dashboard in general folder using title", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": savedFolder.Title,
|
||||
}),
|
||||
FolderId: 0,
|
||||
IsFolder: false,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite folder with dashboard in folder using title", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": savedFolder.Title,
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
IsFolder: false,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite folder with dashboard using id", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedFolder.Id,
|
||||
"title": "new title",
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite dashboard with folder using id", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDash.Id,
|
||||
"title": "new folder title",
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite folder with dashboard using uid", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedFolder.Uid,
|
||||
"title": "new title",
|
||||
}),
|
||||
IsFolder: false,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||
})
|
||||
|
||||
Convey("Should not be able to overwrite dashboard with folder using uid", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDash.Uid,
|
||||
"title": "new folder title",
|
||||
}),
|
||||
IsFolder: true,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||
})
|
||||
|
||||
Convey("Should not be able to save dashboard with same name in the same folder without overwrite", func() {
|
||||
firstSaveCmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": "test dash folder and title",
|
||||
"tags": []interface{}{},
|
||||
"uid": "randomHash",
|
||||
}),
|
||||
FolderId: 3,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&firstSaveCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
secondSaveCmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": "test dash folder and title",
|
||||
"tags": []interface{}{},
|
||||
"uid": "moreRandomHash",
|
||||
}),
|
||||
FolderId: 3,
|
||||
}
|
||||
|
||||
err = SaveDashboard(&secondSaveCmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardWithSameNameInFolderExists)
|
||||
})
|
||||
|
||||
Convey("Should be able to save and update dashboard using same uid", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"uid": "dsfalkjngailuedt",
|
||||
"title": "test dash 23",
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
err = SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to update dashboard using uid", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDash.Uid,
|
||||
"title": "new title",
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should be able to get updated dashboard by uid", func() {
|
||||
query := m.GetDashboardQuery{
|
||||
Uid: savedDash.Uid,
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err := GetDashboard(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(query.Result.Id, ShouldEqual, savedDash.Id)
|
||||
So(query.Result.Title, ShouldEqual, "new title")
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should be able to update dashboard with the same title and folder id", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": "randomHash",
|
||||
"title": "folderId",
|
||||
"style": "light",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
FolderId: 2,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.Result.FolderId, ShouldEqual, 2)
|
||||
|
||||
cmd = m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": cmd.Result.Id,
|
||||
"uid": "randomHash",
|
||||
"title": "folderId",
|
||||
"style": "dark",
|
||||
"version": cmd.Result.Version,
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
FolderId: 2,
|
||||
}
|
||||
|
||||
err = SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to update using uid without id and overwrite", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDash.Uid,
|
||||
"title": "folderId",
|
||||
"version": savedDash.Version,
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
FolderId: savedDash.FolderId,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should retry generation of uid once if it fails.", func() {
|
||||
timesCalled := 0
|
||||
generateNewUid = func() string {
|
||||
@@ -499,6 +181,36 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(len(query.Result), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return error if no dashboard is found for update when dashboard id is greater than zero", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Overwrite: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": float64(123412321),
|
||||
"title": "Expect error",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldEqual, m.ErrDashboardNotFound)
|
||||
})
|
||||
|
||||
Convey("Should not return error if no dashboard is found for update when dashboard id is zero", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Overwrite: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": 0,
|
||||
"title": "New dash",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to get dashboard tags", func() {
|
||||
query := m.GetDashboardTagsQuery{OrgId: 1}
|
||||
|
||||
@@ -512,7 +224,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
Title: "1 test dash folder",
|
||||
OrgId: 1,
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1, OrgRole: m.ROLE_EDITOR},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -529,7 +241,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
FolderIds: []int64{savedFolder.Id},
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1, OrgRole: m.ROLE_EDITOR},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -549,7 +261,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
Convey("should be able to find two dashboards by id", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
DashboardIds: []int64{2, 3},
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1, OrgRole: m.ROLE_EDITOR},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -578,7 +290,10 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to search for starred dashboards", func() {
|
||||
query := search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: 10, OrgId: 1}, IsStarred: true}
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: 10, OrgId: 1, OrgRole: m.ROLE_EDITOR},
|
||||
IsStarred: true,
|
||||
}
|
||||
err := SearchDashboards(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
@@ -624,6 +339,9 @@ func insertTestDashboard(title string, orgId int64, folderId int64, isFolder boo
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cmd.Result.Data.Set("id", cmd.Result.Id)
|
||||
cmd.Result.Data.Set("uid", cmd.Result.Uid)
|
||||
|
||||
return cmd.Result
|
||||
}
|
||||
|
||||
@@ -660,25 +378,6 @@ func createUser(name string, role string, isAdmin bool) m.User {
|
||||
return currentUserCmd.Result
|
||||
}
|
||||
|
||||
func updateTestDashboardWithAcl(dashId int64, userId int64, permissions m.PermissionType) int64 {
|
||||
cmd := &m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: userId,
|
||||
DashboardId: dashId,
|
||||
Permission: permissions,
|
||||
}
|
||||
|
||||
err := SetDashboardAcl(cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return cmd.Result.Id
|
||||
}
|
||||
|
||||
func removeAcl(aclId int64) {
|
||||
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{AclId: aclId, OrgId: 1})
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
func moveDashboard(orgId int64, dashboard *simplejson.Json, newFolderId int64) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func updateTestDashboard(dashboard *m.Dashboard, data map[string]interface{}) {
|
||||
data["uid"] = dashboard.Uid
|
||||
data["id"] = dashboard.Id
|
||||
|
||||
saveCmd := m.SaveDashboardCommand{
|
||||
OrgId: dashboard.OrgId,
|
||||
|
||||
@@ -27,6 +27,9 @@ func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
|
||||
|
||||
datasource := m.DataSource{OrgId: query.OrgId, Id: query.Id}
|
||||
has, err := x.Get(&datasource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !has {
|
||||
return m.ErrDataSourceNotFound
|
||||
|
||||
@@ -1,61 +1,13 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
|
||||
)
|
||||
|
||||
var (
|
||||
dbSqlite = "sqlite"
|
||||
dbMySql = "mysql"
|
||||
dbPostgres = "postgres"
|
||||
)
|
||||
|
||||
func InitTestDB(t *testing.T) *xorm.Engine {
|
||||
selectedDb := dbSqlite
|
||||
//selectedDb := dbMySql
|
||||
//selectedDb := dbPostgres
|
||||
|
||||
var x *xorm.Engine
|
||||
var err error
|
||||
|
||||
// environment variable present for test db?
|
||||
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
|
||||
selectedDb = db
|
||||
}
|
||||
|
||||
switch strings.ToLower(selectedDb) {
|
||||
case dbMySql:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
|
||||
case dbPostgres:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
|
||||
default:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
|
||||
}
|
||||
|
||||
// x.ShowSQL()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init in memory sqllite3 db %v", err)
|
||||
}
|
||||
|
||||
sqlutil.CleanDB(x)
|
||||
|
||||
if err := SetEngine(x); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
Id int64
|
||||
Name string
|
||||
|
||||
@@ -21,7 +21,7 @@ func CreateLoginAttempt(cmd *m.CreateLoginAttemptCommand) error {
|
||||
loginAttempt := m.LoginAttempt{
|
||||
Username: cmd.Username,
|
||||
IpAddress: cmd.IpAddress,
|
||||
Created: getTimeNow(),
|
||||
Created: getTimeNow().Unix(),
|
||||
}
|
||||
|
||||
if _, err := sess.Insert(&loginAttempt); err != nil {
|
||||
@@ -37,8 +37,8 @@ func CreateLoginAttempt(cmd *m.CreateLoginAttemptCommand) error {
|
||||
func DeleteOldLoginAttempts(cmd *m.DeleteOldLoginAttemptsCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
var maxId int64
|
||||
sql := "SELECT max(id) as id FROM login_attempt WHERE created < " + dialect.DateTimeFunc("?")
|
||||
result, err := sess.Query(sql, cmd.OlderThan)
|
||||
sql := "SELECT max(id) as id FROM login_attempt WHERE created < ?"
|
||||
result, err := sess.Query(sql, cmd.OlderThan.Unix())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -66,7 +66,7 @@ func GetUserLoginAttemptCount(query *m.GetUserLoginAttemptCountQuery) error {
|
||||
loginAttempt := new(m.LoginAttempt)
|
||||
total, err := x.
|
||||
Where("username = ?", query.Username).
|
||||
And("created >="+dialect.DateTimeFunc("?"), query.Since).
|
||||
And("created >= ?", query.Since.Unix()).
|
||||
Count(loginAttempt)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -24,3 +24,24 @@ func addTableRenameMigration(mg *Migrator, oldName string, newName string, versi
|
||||
migrationId := fmt.Sprintf("Rename table %s to %s - %s", oldName, newName, versionSuffix)
|
||||
mg.AddMigration(migrationId, NewRenameTableMigration(oldName, newName))
|
||||
}
|
||||
|
||||
func addTableReplaceMigrations(mg *Migrator, from Table, to Table, migrationVersion int64, tableDataMigration map[string]string) {
|
||||
fromV := version(migrationVersion - 1)
|
||||
toV := version(migrationVersion)
|
||||
tmpTableName := to.Name + "_tmp_qwerty"
|
||||
|
||||
createTable := fmt.Sprintf("create %v %v", to.Name, toV)
|
||||
copyTableData := fmt.Sprintf("copy %v %v to %v", to.Name, fromV, toV)
|
||||
dropTable := fmt.Sprintf("drop %v", tmpTableName)
|
||||
|
||||
addDropAllIndicesMigrations(mg, fromV, from)
|
||||
addTableRenameMigration(mg, from.Name, tmpTableName, fromV)
|
||||
mg.AddMigration(createTable, NewAddTableMigration(to))
|
||||
addTableIndicesMigrations(mg, toV, to)
|
||||
mg.AddMigration(copyTableData, NewCopyTableDataMigration(to.Name, tmpTableName, tableDataMigration))
|
||||
mg.AddMigration(dropTable, NewDropTableMigration(tmpTableName))
|
||||
}
|
||||
|
||||
func version(v int64) string {
|
||||
return fmt.Sprintf("v%v", v)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package migrations
|
||||
|
||||
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
import (
|
||||
. "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
)
|
||||
|
||||
func addDashboardMigration(mg *Migrator) {
|
||||
var dashboardV1 = Table{
|
||||
@@ -181,15 +183,34 @@ func addDashboardMigration(mg *Migrator) {
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "dashboard_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||
{Name: "name", Type: DB_NVarchar, Length: 150, Nullable: false},
|
||||
{Name: "external_id", Type: DB_Text, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{},
|
||||
}
|
||||
|
||||
mg.AddMigration("create dashboard_provisioning", NewAddTableMigration(dashboardExtrasTable))
|
||||
|
||||
dashboardExtrasTableV2 := Table{
|
||||
Name: "dashboard_provisioning",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "dashboard_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "name", Type: DB_NVarchar, Length: 150, Nullable: false},
|
||||
{Name: "external_id", Type: DB_Text, Nullable: false},
|
||||
{Name: "updated", Type: DB_Int, Default: "0", Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"dashboard_id"}},
|
||||
{Cols: []string{"dashboard_id", "name"}, Type: IndexType},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create dashboard_provisioning", NewAddTableMigration(dashboardExtrasTable))
|
||||
addTableReplaceMigrations(mg, dashboardExtrasTable, dashboardExtrasTableV2, 2, map[string]string{
|
||||
"id": "id",
|
||||
"dashboard_id": "dashboard_id",
|
||||
"name": "name",
|
||||
"external_id": "external_id",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,4 +20,23 @@ func addLoginAttemptMigrations(mg *Migrator) {
|
||||
mg.AddMigration("create login attempt table", NewAddTableMigration(loginAttemptV1))
|
||||
// add indices
|
||||
mg.AddMigration("add index login_attempt.username", NewAddIndexMigration(loginAttemptV1, loginAttemptV1.Indices[0]))
|
||||
|
||||
loginAttemptV2 := Table{
|
||||
Name: "login_attempt",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "username", Type: DB_NVarchar, Length: 190, Nullable: false},
|
||||
{Name: "ip_address", Type: DB_NVarchar, Length: 30, Nullable: false},
|
||||
{Name: "created", Type: DB_Int, Default: "0", Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"username"}},
|
||||
},
|
||||
}
|
||||
|
||||
addTableReplaceMigrations(mg, loginAttemptV1, loginAttemptV2, 2, map[string]string{
|
||||
"id": "id",
|
||||
"username": "username",
|
||||
"ip_address": "ip_address",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,13 +14,15 @@ import (
|
||||
var indexTypes = []string{"Unknown", "INDEX", "UNIQUE INDEX"}
|
||||
|
||||
func TestMigrations(t *testing.T) {
|
||||
//log.NewLogger(0, "console", `{"level": 0}`)
|
||||
|
||||
testDBs := []sqlutil.TestDB{
|
||||
sqlutil.TestDB_Sqlite3,
|
||||
}
|
||||
|
||||
for _, testDB := range testDBs {
|
||||
sql := `select count(*) as count from migration_log`
|
||||
r := struct {
|
||||
Count int64
|
||||
}{}
|
||||
|
||||
Convey("Initial "+testDB.DriverName+" migration", t, func() {
|
||||
x, err := xorm.NewEngine(testDB.DriverName, testDB.ConnStr)
|
||||
@@ -28,30 +30,31 @@ func TestMigrations(t *testing.T) {
|
||||
|
||||
sqlutil.CleanDB(x)
|
||||
|
||||
has, err := x.SQL(sql).Get(&r)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
mg := NewMigrator(x)
|
||||
AddMigrations(mg)
|
||||
|
||||
err = mg.Start()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// tables, err := x.DBMetas()
|
||||
// So(err, ShouldBeNil)
|
||||
//
|
||||
// fmt.Printf("\nDB Schema after migration: table count: %v\n", len(tables))
|
||||
//
|
||||
// for _, table := range tables {
|
||||
// fmt.Printf("\nTable: %v \n", table.Name)
|
||||
// for _, column := range table.Columns() {
|
||||
// fmt.Printf("\t %v \n", column.String(x.Dialect()))
|
||||
// }
|
||||
//
|
||||
// if len(table.Indexes) > 0 {
|
||||
// fmt.Printf("\n\tIndexes:\n")
|
||||
// for _, index := range table.Indexes {
|
||||
// fmt.Printf("\t %v (%v) %v \n", index.Name, strings.Join(index.Cols, ","), indexTypes[index.Type])
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
has, err = x.SQL(sql).Get(&r)
|
||||
So(err, ShouldBeNil)
|
||||
So(has, ShouldBeTrue)
|
||||
expectedMigrations := mg.MigrationsCount() - 2 //we currently skip to migrations. We should rewrite skipped migrations to write in the log as well. until then we have to keep this
|
||||
So(r.Count, ShouldEqual, expectedMigrations)
|
||||
|
||||
mg = NewMigrator(x)
|
||||
AddMigrations(mg)
|
||||
|
||||
err = mg.Start()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
has, err = x.SQL(sql).Get(&r)
|
||||
So(err, ShouldBeNil)
|
||||
So(has, ShouldBeTrue)
|
||||
So(r.Count, ShouldEqual, expectedMigrations)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ func NewMigrator(engine *xorm.Engine) *Migrator {
|
||||
return mg
|
||||
}
|
||||
|
||||
func (mg *Migrator) MigrationsCount() int {
|
||||
return len(mg.migrations)
|
||||
}
|
||||
|
||||
func (mg *Migrator) AddMigration(id string, m Migration) {
|
||||
m.SetId(id)
|
||||
mg.migrations = append(mg.migrations, m)
|
||||
|
||||
@@ -199,10 +199,13 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
|
||||
dash1 := insertTestDashboard("1 test dash", ac1.OrgId, 0, false, "prod", "webapp")
|
||||
dash2 := insertTestDashboard("2 test dash", ac3.OrgId, 0, false, "prod", "webapp")
|
||||
|
||||
err = testHelperUpdateDashboardAcl(dash1.Id, m.DashboardAcl{DashboardId: dash1.Id, OrgId: ac1.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
|
||||
err = testHelperUpdateDashboardAcl(dash2.Id, m.DashboardAcl{DashboardId: dash2.Id, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When org user is deleted", func() {
|
||||
@@ -234,3 +237,11 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testHelperUpdateDashboardAcl(dashboardId int64, items ...m.DashboardAcl) error {
|
||||
cmd := m.UpdateDashboardAclCommand{DashboardId: dashboardId}
|
||||
for _, item := range items {
|
||||
cmd.Items = append(cmd.Items, &item)
|
||||
}
|
||||
return UpdateDashboardAcl(&cmd)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
@@ -9,6 +8,7 @@ import (
|
||||
|
||||
// SearchBuilder is a builder/object mother that builds a dashboard search query
|
||||
type SearchBuilder struct {
|
||||
SqlBuilder
|
||||
tags []string
|
||||
isStarred bool
|
||||
limit int
|
||||
@@ -18,14 +18,14 @@ type SearchBuilder struct {
|
||||
whereTypeFolder bool
|
||||
whereTypeDash bool
|
||||
whereFolderIds []int64
|
||||
sql bytes.Buffer
|
||||
params []interface{}
|
||||
permission m.PermissionType
|
||||
}
|
||||
|
||||
func NewSearchBuilder(signedInUser *m.SignedInUser, limit int) *SearchBuilder {
|
||||
func NewSearchBuilder(signedInUser *m.SignedInUser, limit int, permission m.PermissionType) *SearchBuilder {
|
||||
searchBuilder := &SearchBuilder{
|
||||
signedInUser: signedInUser,
|
||||
limit: limit,
|
||||
permission: permission,
|
||||
}
|
||||
|
||||
return searchBuilder
|
||||
@@ -153,10 +153,7 @@ func (sb *SearchBuilder) buildMainQuery() {
|
||||
sb.sql.WriteString(` WHERE `)
|
||||
sb.buildSearchWhereClause()
|
||||
|
||||
sb.sql.WriteString(`
|
||||
LIMIT ?) as ids
|
||||
INNER JOIN dashboard on ids.id = dashboard.id
|
||||
`)
|
||||
sb.sql.WriteString(` LIMIT ?) as ids INNER JOIN dashboard on ids.id = dashboard.id `)
|
||||
sb.params = append(sb.params, sb.limit)
|
||||
}
|
||||
|
||||
@@ -176,23 +173,7 @@ func (sb *SearchBuilder) buildSearchWhereClause() {
|
||||
}
|
||||
}
|
||||
|
||||
if sb.signedInUser.OrgRole != m.ROLE_ADMIN {
|
||||
allowedDashboardsSubQuery := ` AND (dashboard.has_acl = ` + dialect.BooleanStr(false) + ` OR dashboard.id in (
|
||||
SELECT distinct d.id AS DashboardId
|
||||
FROM dashboard AS d
|
||||
LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id
|
||||
LEFT JOIN team_member as ugm on ugm.team_id = da.team_id
|
||||
LEFT JOIN org_user ou on ou.role = da.role
|
||||
WHERE
|
||||
d.has_acl = ` + dialect.BooleanStr(true) + ` and
|
||||
(da.user_id = ? or ugm.user_id = ? or ou.id is not null)
|
||||
and d.org_id = ?
|
||||
)
|
||||
)`
|
||||
|
||||
sb.sql.WriteString(allowedDashboardsSubQuery)
|
||||
sb.params = append(sb.params, sb.signedInUser.UserId, sb.signedInUser.UserId, sb.signedInUser.OrgId)
|
||||
}
|
||||
sb.writeDashboardPermissionFilter(sb.signedInUser, sb.permission)
|
||||
|
||||
if len(sb.whereTitle) > 0 {
|
||||
sb.sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?")
|
||||
|
||||
@@ -16,7 +16,8 @@ func TestSearchBuilder(t *testing.T) {
|
||||
OrgId: 1,
|
||||
UserId: 1,
|
||||
}
|
||||
sb := NewSearchBuilder(signedInUser, 1000)
|
||||
|
||||
sb := NewSearchBuilder(signedInUser, 1000, m.PERMISSION_VIEW)
|
||||
|
||||
Convey("When building a normal search", func() {
|
||||
sql, params := sb.IsStarred().WithTitle("test").ToSql()
|
||||
|
||||
75
pkg/services/sqlstore/sqlbuilder.go
Normal file
75
pkg/services/sqlstore/sqlbuilder.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type SqlBuilder struct {
|
||||
sql bytes.Buffer
|
||||
params []interface{}
|
||||
}
|
||||
|
||||
func (sb *SqlBuilder) Write(sql string, params ...interface{}) {
|
||||
sb.sql.WriteString(sql)
|
||||
|
||||
if len(params) > 0 {
|
||||
sb.params = append(sb.params, params...)
|
||||
}
|
||||
}
|
||||
|
||||
func (sb *SqlBuilder) GetSqlString() string {
|
||||
return sb.sql.String()
|
||||
}
|
||||
|
||||
func (sb *SqlBuilder) AddParams(params ...interface{}) {
|
||||
sb.params = append(sb.params, params...)
|
||||
}
|
||||
|
||||
func (sb *SqlBuilder) writeDashboardPermissionFilter(user *m.SignedInUser, permission m.PermissionType) {
|
||||
|
||||
if user.OrgRole == m.ROLE_ADMIN {
|
||||
return
|
||||
}
|
||||
|
||||
okRoles := []interface{}{user.OrgRole}
|
||||
|
||||
if user.OrgRole == m.ROLE_EDITOR {
|
||||
okRoles = append(okRoles, m.ROLE_VIEWER)
|
||||
}
|
||||
|
||||
falseStr := dialect.BooleanStr(false)
|
||||
|
||||
sb.sql.WriteString(` AND
|
||||
(
|
||||
dashboard.id IN (
|
||||
SELECT distinct d.id AS DashboardId
|
||||
FROM dashboard AS d
|
||||
LEFT JOIN dashboard folder on folder.id = d.folder_id
|
||||
LEFT JOIN dashboard_acl AS da ON
|
||||
da.dashboard_id = d.id OR
|
||||
da.dashboard_id = d.folder_id OR
|
||||
(
|
||||
-- include default permissions -->
|
||||
da.org_id = -1 AND (
|
||||
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
|
||||
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
|
||||
)
|
||||
)
|
||||
LEFT JOIN team_member as ugm on ugm.team_id = da.team_id
|
||||
WHERE
|
||||
d.org_id = ? AND
|
||||
da.permission >= ? AND
|
||||
(
|
||||
da.user_id = ? OR
|
||||
ugm.user_id = ? OR
|
||||
da.role IN (?` + strings.Repeat(",?", len(okRoles)-1) + `)
|
||||
)
|
||||
)
|
||||
)`)
|
||||
|
||||
sb.params = append(sb.params, user.OrgId, permission, user.UserId, user.UserId)
|
||||
sb.params = append(sb.params, okRoles...)
|
||||
}
|
||||
@@ -7,14 +7,15 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
@@ -101,7 +102,6 @@ func SetEngine(engine *xorm.Engine) (err error) {
|
||||
|
||||
// Init repo instances
|
||||
annotations.SetRepository(&SqlAnnotationRepo{})
|
||||
dashboards.SetRepository(&dashboards.DashboardRepository{})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -216,3 +216,46 @@ func LoadConfig() {
|
||||
DbCfg.ServerCertName = sec.Key("server_cert_name").String()
|
||||
DbCfg.Path = sec.Key("path").MustString("data/grafana.db")
|
||||
}
|
||||
|
||||
var (
|
||||
dbSqlite = "sqlite"
|
||||
dbMySql = "mysql"
|
||||
dbPostgres = "postgres"
|
||||
)
|
||||
|
||||
func InitTestDB(t *testing.T) *xorm.Engine {
|
||||
selectedDb := dbSqlite
|
||||
//selectedDb := dbMySql
|
||||
//selectedDb := dbPostgres
|
||||
|
||||
var x *xorm.Engine
|
||||
var err error
|
||||
|
||||
// environment variable present for test db?
|
||||
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
|
||||
selectedDb = db
|
||||
}
|
||||
|
||||
switch strings.ToLower(selectedDb) {
|
||||
case dbMySql:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
|
||||
case dbPostgres:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
|
||||
default:
|
||||
x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
|
||||
}
|
||||
|
||||
// x.ShowSQL()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init in memory sqllite3 db %v", err)
|
||||
}
|
||||
|
||||
sqlutil.CleanDB(x)
|
||||
|
||||
if err := SetEngine(x); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ type TestDB struct {
|
||||
ConnStr string
|
||||
}
|
||||
|
||||
var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:?_loc=Local"}
|
||||
var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?collation=utf8mb4_unicode_ci&loc=Local"}
|
||||
var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:"}
|
||||
var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?collation=utf8mb4_unicode_ci"}
|
||||
var TestDB_Postgres = TestDB{DriverName: "postgres", ConnStr: "user=grafanatest password=grafanatest host=localhost port=5432 dbname=grafanatest sslmode=disable"}
|
||||
|
||||
func CleanDB(x *xorm.Engine) {
|
||||
|
||||
@@ -78,11 +78,12 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteTeam will delete a team, its member and any permissions connected to the team
|
||||
func DeleteTeam(cmd *m.DeleteTeamCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", cmd.OrgId, cmd.Id); err != nil {
|
||||
if teamExists, err := teamExists(cmd.OrgId, cmd.Id, sess); err != nil {
|
||||
return err
|
||||
} else if len(res) != 1 {
|
||||
} else if !teamExists {
|
||||
return m.ErrTeamNotFound
|
||||
}
|
||||
|
||||
@@ -102,6 +103,16 @@ func DeleteTeam(cmd *m.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 {
|
||||
return false, err
|
||||
} else if len(res) != 1 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession) (bool, error) {
|
||||
var team m.Team
|
||||
exists, err := sess.Where("org_id=? and name=?", orgId, name).Get(&team)
|
||||
@@ -190,6 +201,7 @@ func GetTeamById(query *m.GetTeamByIdQuery) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTeamsByUser is used by the Guardian when checking a users' permissions
|
||||
func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = make([]*m.Team, 0)
|
||||
|
||||
@@ -205,6 +217,7 @@ func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTeamMember adds a user to a team
|
||||
func AddTeamMember(cmd *m.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 {
|
||||
@@ -213,9 +226,9 @@ func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
|
||||
return m.ErrTeamMemberAlreadyAdded
|
||||
}
|
||||
|
||||
if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", cmd.OrgId, cmd.TeamId); err != nil {
|
||||
if teamExists, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
|
||||
return err
|
||||
} else if len(res) != 1 {
|
||||
} else if !teamExists {
|
||||
return m.ErrTeamNotFound
|
||||
}
|
||||
|
||||
@@ -232,18 +245,30 @@ func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveTeamMember removes a member from a team
|
||||
func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if teamExists, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
|
||||
return err
|
||||
} else if !teamExists {
|
||||
return m.ErrTeamNotFound
|
||||
}
|
||||
|
||||
var rawSql = "DELETE FROM team_member WHERE org_id=? and team_id=? and user_id=?"
|
||||
_, err := sess.Exec(rawSql, cmd.OrgId, cmd.TeamId, cmd.UserId)
|
||||
res, err := sess.Exec(rawSql, cmd.OrgId, cmd.TeamId, cmd.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, err := res.RowsAffected()
|
||||
if rows == 0 {
|
||||
return m.ErrTeamMemberNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// GetTeamMembers return a list of members for the specified team
|
||||
func GetTeamMembers(query *m.GetTeamMembersQuery) error {
|
||||
query.Result = make([]*m.TeamMemberDTO, 0)
|
||||
sess := x.Table("team_member")
|
||||
|
||||
@@ -84,13 +84,16 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to remove users from a group", func() {
|
||||
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = RemoveTeamMember(&m.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := &m.GetTeamMembersQuery{TeamId: group1.Result.Id}
|
||||
err = GetTeamMembers(q1)
|
||||
q2 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: group1.Result.Id}
|
||||
err = GetTeamMembers(q2)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q1.Result), ShouldEqual, 0)
|
||||
So(len(q2.Result), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to remove a group with users and permissions", func() {
|
||||
@@ -99,7 +102,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]})
|
||||
So(err, ShouldBeNil)
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId})
|
||||
err = testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId})
|
||||
|
||||
err = DeleteTeam(&m.DeleteTeamCommand{OrgId: testOrgId, Id: groupId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestUserDataAccess(t *testing.T) {
|
||||
err = AddOrgUser(&m.AddOrgUserCommand{LoginOrEmail: users[0].Login, Role: m.ROLE_VIEWER, OrgId: users[0].OrgId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permission: m.PERMISSION_EDIT})
|
||||
testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = SavePreferences(&m.SavePreferencesCommand{UserId: users[0].Id, OrgId: users[0].OrgId, HomeDashboardId: 1, Theme: "dark"})
|
||||
|
||||
@@ -98,11 +98,13 @@ func init() {
|
||||
"AWS/SES": {"Bounce", "Complaint", "Delivery", "Reject", "Send"},
|
||||
"AWS/SNS": {"NumberOfMessagesPublished", "PublishSize", "NumberOfNotificationsDelivered", "NumberOfNotificationsFailed"},
|
||||
"AWS/SQS": {"NumberOfMessagesSent", "SentMessageSize", "NumberOfMessagesReceived", "NumberOfEmptyReceives", "NumberOfMessagesDeleted", "ApproximateAgeOfOldestMessage", "ApproximateNumberOfMessagesDelayed", "ApproximateNumberOfMessagesVisible", "ApproximateNumberOfMessagesNotVisible"},
|
||||
"AWS/States": {"ExecutionTime", "ExecutionThrottled", "ExecutionsAborted", "ExecutionsFailed", "ExecutionsStarted", "ExecutionsSucceeded", "ExecutionsTimedOut", "ActivityRunTime", "ActivityScheduleTime", "ActivityTime", "ActivitiesFailed", "ActivitiesHeartbeatTimedOut", "ActivitiesScheduled", "ActivitiesScheduled", "ActivitiesSucceeded", "ActivitiesTimedOut", "LambdaFunctionRunTime", "LambdaFunctionScheduleTime", "LambdaFunctionTime", "LambdaFunctionsFailed", "LambdaFunctionsHeartbeatTimedOut", "LambdaFunctionsScheduled", "LambdaFunctionsStarted", "LambdaFunctionsSucceeded", "LambdaFunctionsTimedOut"},
|
||||
"AWS/StorageGateway": {"CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "CloudBytesDownloaded", "CloudDownloadLatency", "CloudBytesUploaded", "UploadBufferFree", "UploadBufferPercentUsed", "UploadBufferUsed", "QueuedWrites", "ReadBytes", "ReadTime", "TotalCacheSize", "WriteBytes", "WriteTime", "TimeSinceLastRecoveryPoint", "WorkingStorageFree", "WorkingStoragePercentUsed", "WorkingStorageUsed",
|
||||
"CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "ReadBytes", "ReadTime", "WriteBytes", "WriteTime", "QueuedWrites"},
|
||||
"AWS/SWF": {"DecisionTaskScheduleToStartTime", "DecisionTaskStartToCloseTime", "DecisionTasksCompleted", "StartedDecisionTasksTimedOutOnClose", "WorkflowStartToCloseTime", "WorkflowsCanceled", "WorkflowsCompleted", "WorkflowsContinuedAsNew", "WorkflowsFailed", "WorkflowsTerminated", "WorkflowsTimedOut",
|
||||
"ActivityTaskScheduleToCloseTime", "ActivityTaskScheduleToStartTime", "ActivityTaskStartToCloseTime", "ActivityTasksCanceled", "ActivityTasksCompleted", "ActivityTasksFailed", "ScheduledActivityTasksTimedOutOnClose", "ScheduledActivityTasksTimedOutOnStart", "StartedActivityTasksTimedOutOnClose", "StartedActivityTasksTimedOutOnHeartbeat"},
|
||||
"AWS/VPN": {"TunnelState", "TunnelDataIn", "TunnelDataOut"},
|
||||
"Rekognition": {"SuccessfulRequestCount", "ThrottledCount", "ResponseTime", "DetectedFaceCount", "DetectedLabelCount", "ServerErrorCount", "UserErrorCount"},
|
||||
"WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests"},
|
||||
"AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"},
|
||||
"KMS": {"SecondsUntilKeyMaterialExpiration"},
|
||||
@@ -145,9 +147,11 @@ func init() {
|
||||
"AWS/SES": {},
|
||||
"AWS/SNS": {"Application", "Platform", "TopicName"},
|
||||
"AWS/SQS": {"QueueName"},
|
||||
"AWS/States": {"StateMachineArn", "ActivityArn", "LambdaFunctionArn"},
|
||||
"AWS/StorageGateway": {"GatewayId", "GatewayName", "VolumeId"},
|
||||
"AWS/SWF": {"Domain", "WorkflowTypeName", "WorkflowTypeVersion", "ActivityTypeName", "ActivityTypeVersion"},
|
||||
"AWS/VPN": {"VpnId", "TunnelIpAddress"},
|
||||
"Rekognition": {},
|
||||
"WAF": {"Rule", "WebACL"},
|
||||
"AWS/WorkSpaces": {"DirectoryId", "WorkspaceId"},
|
||||
"KMS": {"KeyId"},
|
||||
|
||||
@@ -220,14 +220,14 @@ func (e PostgresQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *co
|
||||
case time.Time:
|
||||
timestamp = float64(columnValue.UnixNano() / 1e6)
|
||||
default:
|
||||
return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp")
|
||||
return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue)
|
||||
}
|
||||
|
||||
if metricIndex >= 0 {
|
||||
if columnValue, ok := values[metricIndex].(string); ok == true {
|
||||
metric = columnValue
|
||||
} else {
|
||||
return fmt.Errorf("Column metric must be of type char,varchar or text")
|
||||
return fmt.Errorf("Column metric must be of type char,varchar or text, got: %T %v", values[metricIndex], values[metricIndex])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/teris-io/shortid"
|
||||
)
|
||||
|
||||
var allowedChars = shortid.DefaultABC
|
||||
|
||||
var validUidPattern = regexp.MustCompile(`^[a-zA-Z0-9\-\_]*$`).MatchString
|
||||
|
||||
func init() {
|
||||
gen, _ := shortid.New(1, shortid.DefaultABC, 1)
|
||||
gen, _ := shortid.New(1, allowedChars, 1)
|
||||
shortid.SetDefault(gen)
|
||||
}
|
||||
|
||||
// IsValidShortUid checks if short unique identifier contains valid characters
|
||||
func IsValidShortUid(uid string) bool {
|
||||
if !validUidPattern(uid) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GenerateShortUid generates a short unique identifier.
|
||||
func GenerateShortUid() string {
|
||||
return shortid.MustGenerate()
|
||||
|
||||
11
pkg/util/shortid_generator_test.go
Normal file
11
pkg/util/shortid_generator_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAllowedCharMatchesUidPattern(t *testing.T) {
|
||||
for _, c := range allowedChars {
|
||||
if !IsValidShortUid(string(c)) {
|
||||
t.Fatalf("charset for creating new shortids contains chars not present in uid pattern")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user