Merge branch 'master' into master
This commit is contained in:
@@ -12,7 +12,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
Convey("Testing Alerting data access", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
testDash := insertTestDashboard("dashboard with alerts", 1, "alert")
|
||||
testDash := insertTestDashboard("dashboard with alerts", 1, 0, false, "alert")
|
||||
|
||||
items := []*m.Alert{
|
||||
{
|
||||
@@ -192,7 +192,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
|
||||
err = DeleteDashboard(&m.DeleteDashboardCommand{
|
||||
OrgId: 1,
|
||||
Slug: testDash.Slug,
|
||||
Id: testDash.Id,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
@@ -70,6 +68,11 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
}
|
||||
}
|
||||
|
||||
err = setHasAcl(sess, dash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentVersion := dash.Version
|
||||
affectedRows := int64(0)
|
||||
|
||||
@@ -79,9 +82,14 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
dash.Data.Set("version", dash.Version)
|
||||
affectedRows, err = sess.Insert(dash)
|
||||
} else {
|
||||
dash.Version += 1
|
||||
dash.Version++
|
||||
dash.Data.Set("version", dash.Version)
|
||||
affectedRows, err = sess.Id(dash.Id).Update(dash)
|
||||
|
||||
if !cmd.UpdatedAt.IsZero() {
|
||||
dash.Updated = cmd.UpdatedAt
|
||||
}
|
||||
|
||||
affectedRows, err = sess.MustCols("folder_id", "has_acl").Id(dash.Id).Update(dash)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -110,7 +118,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
// delete existing tabs
|
||||
// delete existing tags
|
||||
_, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -125,13 +133,37 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Result = dash
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
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}
|
||||
has, err := x.Get(&dashboard)
|
||||
@@ -148,64 +180,76 @@ func GetDashboard(query *m.GetDashboardQuery) error {
|
||||
}
|
||||
|
||||
type DashboardSearchProjection struct {
|
||||
Id int64
|
||||
Title string
|
||||
Slug string
|
||||
Term string
|
||||
Id int64
|
||||
Title string
|
||||
Slug string
|
||||
Term string
|
||||
IsFolder bool
|
||||
FolderId int64
|
||||
FolderSlug string
|
||||
FolderTitle string
|
||||
}
|
||||
|
||||
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
sql.WriteString(`SELECT
|
||||
dashboard.id,
|
||||
dashboard.title,
|
||||
dashboard.slug,
|
||||
dashboard_tag.term
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id`)
|
||||
|
||||
if query.IsStarred {
|
||||
sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
|
||||
func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
|
||||
limit := query.Limit
|
||||
if limit == 0 {
|
||||
limit = 1000
|
||||
}
|
||||
|
||||
sql.WriteString(` WHERE dashboard.org_id=?`)
|
||||
|
||||
params = append(params, query.OrgId)
|
||||
sb := NewSearchBuilder(query.SignedInUser, limit).
|
||||
WithTags(query.Tags).
|
||||
WithDashboardIdsIn(query.DashboardIds)
|
||||
|
||||
if query.IsStarred {
|
||||
sql.WriteString(` AND star.user_id=?`)
|
||||
params = append(params, query.UserId)
|
||||
}
|
||||
|
||||
if len(query.DashboardIds) > 0 {
|
||||
sql.WriteString(" AND (")
|
||||
for i, dashboardId := range query.DashboardIds {
|
||||
if i != 0 {
|
||||
sql.WriteString(" OR")
|
||||
}
|
||||
|
||||
sql.WriteString(" dashboard.id = ?")
|
||||
params = append(params, dashboardId)
|
||||
}
|
||||
sql.WriteString(")")
|
||||
sb.IsStarred()
|
||||
}
|
||||
|
||||
if len(query.Title) > 0 {
|
||||
sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?")
|
||||
params = append(params, "%"+query.Title+"%")
|
||||
sb.WithTitle(query.Title)
|
||||
}
|
||||
|
||||
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 1000"))
|
||||
if len(query.Type) > 0 {
|
||||
sb.WithType(query.Type)
|
||||
}
|
||||
|
||||
if len(query.FolderIds) > 0 {
|
||||
sb.WithFolderIds(query.FolderIds)
|
||||
}
|
||||
|
||||
var res []DashboardSearchProjection
|
||||
|
||||
err := x.Sql(sql.String(), params...).Find(&res)
|
||||
sql, params := sb.ToSql()
|
||||
err := x.Sql(sql, params...).Find(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
res, err := findDashboards(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
makeQueryResult(query, res)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHitType(item DashboardSearchProjection) search.HitType {
|
||||
var hitType search.HitType
|
||||
if item.IsFolder {
|
||||
hitType = search.DashHitFolder
|
||||
} else {
|
||||
hitType = search.DashHitDB
|
||||
}
|
||||
|
||||
return hitType
|
||||
}
|
||||
|
||||
func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []DashboardSearchProjection) {
|
||||
query.Result = make([]*search.Hit, 0)
|
||||
hits := make(map[int64]*search.Hit)
|
||||
|
||||
@@ -213,11 +257,15 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
hit, exists := hits[item.Id]
|
||||
if !exists {
|
||||
hit = &search.Hit{
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: "db/" + item.Slug,
|
||||
Type: search.DashHitDB,
|
||||
Tags: []string{},
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: "db/" + item.Slug,
|
||||
Slug: item.Slug,
|
||||
Type: getHitType(item),
|
||||
FolderId: item.FolderId,
|
||||
FolderTitle: item.FolderTitle,
|
||||
FolderSlug: item.FolderSlug,
|
||||
Tags: []string{},
|
||||
}
|
||||
query.Result = append(query.Result, hit)
|
||||
hits[item.Id] = hit
|
||||
@@ -226,8 +274,6 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
hit.Tags = append(hit.Tags, item.Term)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
@@ -247,7 +293,7 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
|
||||
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
dashboard := m.Dashboard{Slug: cmd.Slug, OrgId: cmd.OrgId}
|
||||
dashboard := m.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
|
||||
has, err := sess.Get(&dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -261,6 +307,7 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
||||
"DELETE FROM dashboard WHERE id = ?",
|
||||
"DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?",
|
||||
"DELETE FROM dashboard_version WHERE dashboard_id = ?",
|
||||
"DELETE FROM dashboard WHERE folder_id = ?",
|
||||
"DELETE FROM annotation WHERE dashboard_id = ?",
|
||||
}
|
||||
|
||||
@@ -298,8 +345,9 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
|
||||
|
||||
func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error {
|
||||
var dashboards = make([]*m.Dashboard, 0)
|
||||
whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false)
|
||||
|
||||
err := x.Where("org_id=? AND plugin_id=?", query.OrgId, query.PluginId).Find(&dashboards)
|
||||
err := x.Where(whereExpr, query.OrgId, query.PluginId).Find(&dashboards)
|
||||
query.Result = dashboards
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
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)
|
||||
}
|
||||
|
||||
func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
// delete existing items
|
||||
_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", cmd.DashboardId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range cmd.Items {
|
||||
if item.UserId == 0 && item.TeamId == 0 && !item.Role.IsValid() {
|
||||
return m.ErrDashboardAclInfoMissing
|
||||
}
|
||||
|
||||
if item.DashboardId == 0 {
|
||||
return m.ErrDashboardPermissionDashboardEmpty
|
||||
}
|
||||
|
||||
sess.Nullable("user_id", "team_id")
|
||||
if _, err := sess.Insert(item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
dashboardFilter := fmt.Sprintf(`IN (
|
||||
SELECT %d
|
||||
UNION
|
||||
SELECT folder_id from dashboard where id = %d
|
||||
)`, query.DashboardId, query.DashboardId)
|
||||
|
||||
rawSQL := `
|
||||
SELECT
|
||||
da.id,
|
||||
da.org_id,
|
||||
da.dashboard_id,
|
||||
da.user_id,
|
||||
da.team_id,
|
||||
da.permission,
|
||||
da.role,
|
||||
da.created,
|
||||
da.updated,
|
||||
u.login AS user_login,
|
||||
u.email AS user_email,
|
||||
ug.name AS team
|
||||
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
|
||||
WHERE dashboard_id ` + dashboardFilter + ` AND da.org_id = ?
|
||||
|
||||
-- Also include default permission if has_acl = 0
|
||||
|
||||
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
|
||||
FROM dashboard_acl as da,
|
||||
dashboard as dash
|
||||
LEFT 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
|
||||
`
|
||||
|
||||
query.Result = make([]*m.DashboardAclInfoDTO, 0)
|
||||
err := x.SQL(rawSQL, query.OrgId, query.DashboardId).Find(&query.Result)
|
||||
|
||||
for _, p := range query.Result {
|
||||
p.PermissionName = p.Permission.String()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func TestDashboardAclDataAccess(t *testing.T) {
|
||||
Convey("Testing DB", t, func() {
|
||||
InitTestDB(t)
|
||||
Convey("Given a dashboard folder and a user", func() {
|
||||
currentUser := createUser("viewer", "Viewer", false)
|
||||
savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
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{
|
||||
OrgId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldEqual, m.ErrDashboardAclInfoMissing)
|
||||
})
|
||||
|
||||
Convey("Given dashboard folder with default permissions", func() {
|
||||
Convey("When reading dashboard acl should include acl for parent folder", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1}
|
||||
|
||||
err := GetDashboardAclInfoList(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
defaultPermissionsId := -1
|
||||
So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given dashboard folder permission", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When reading dashboard acl should include acl for parent folder", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1}
|
||||
|
||||
err := GetDashboardAclInfoList(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
So(query.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
})
|
||||
|
||||
Convey("Given child dashboard permission", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: childDash.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When reading dashboard acl should include acl for parent folder and child", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{OrgId: 1, DashboardId: childDash.Id}
|
||||
|
||||
err := GetDashboardAclInfoList(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
So(query.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, childDash.Id)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given child dashboard permission in folder with no permissions", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: childDash.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When reading dashboard acl should include default acl for parent folder and the child acl", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{OrgId: 1, DashboardId: childDash.Id}
|
||||
|
||||
err := GetDashboardAclInfoList(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
defaultPermissionsId := -1
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
|
||||
So(query.Result[2].DashboardId, ShouldEqual, childDash.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should be able to add dashboard permission", func() {
|
||||
setDashAclCmd := m.SetDashboardAclCommand{
|
||||
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)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].PermissionName, ShouldEqual, "Edit")
|
||||
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,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q3)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a team", func() {
|
||||
group1 := m.CreateTeamCommand{Name: "group1 name", OrgId: 1}
|
||||
err := CreateTeam(&group1)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should be able to add a user permission for a team", func() {
|
||||
setDashAclCmd := m.SetDashboardAclCommand{
|
||||
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}
|
||||
err = GetDashboardAclInfoList(q1)
|
||||
So(err, ShouldBeNil)
|
||||
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{
|
||||
OrgId: 1,
|
||||
TeamId: group1.Result.Id,
|
||||
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].TeamId, ShouldEqual, group1.Result.Id)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -3,44 +3,39 @@ package sqlstore
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
"tags": tags,
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return cmd.Result
|
||||
}
|
||||
|
||||
func TestDashboardDataAccess(t *testing.T) {
|
||||
var x *xorm.Engine
|
||||
|
||||
Convey("Testing DB", t, func() {
|
||||
InitTestDB(t)
|
||||
x = InitTestDB(t)
|
||||
|
||||
Convey("Given saved dashboard", func() {
|
||||
savedDash := insertTestDashboard("test dash 23", 1, "prod", "webapp")
|
||||
insertTestDashboard("test dash 45", 1, "prod")
|
||||
insertTestDashboard("test dash 67", 1, "prod", "webapp")
|
||||
savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
savedDash := insertTestDashboard("test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
insertTestDashboard("test dash 45", 1, savedFolder.Id, false, "prod")
|
||||
insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp")
|
||||
|
||||
Convey("Should return dashboard model", func() {
|
||||
So(savedDash.Title, ShouldEqual, "test dash 23")
|
||||
So(savedDash.Slug, ShouldEqual, "test-dash-23")
|
||||
So(savedDash.Id, ShouldNotEqual, 0)
|
||||
So(savedDash.IsFolder, ShouldBeFalse)
|
||||
So(savedDash.FolderId, ShouldBeGreaterThan, 0)
|
||||
|
||||
So(savedFolder.Title, ShouldEqual, "1 test dash folder")
|
||||
So(savedFolder.Slug, ShouldEqual, "1-test-dash-folder")
|
||||
So(savedFolder.Id, ShouldNotEqual, 0)
|
||||
So(savedFolder.IsFolder, ShouldBeTrue)
|
||||
So(savedFolder.FolderId, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to get dashboard", func() {
|
||||
@@ -54,15 +49,14 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
So(query.Result.Title, ShouldEqual, "test dash 23")
|
||||
So(query.Result.Slug, ShouldEqual, "test-dash-23")
|
||||
So(query.Result.IsFolder, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Should be able to delete dashboard", func() {
|
||||
insertTestDashboard("delete me", 1, "delete this")
|
||||
|
||||
dashboardSlug := slug.Make("delete me")
|
||||
dash := insertTestDashboard("delete me", 1, 0, false, "delete this")
|
||||
|
||||
err := DeleteDashboard(&m.DeleteDashboardCommand{
|
||||
Slug: dashboardSlug,
|
||||
Id: dash.Id,
|
||||
OrgId: 1,
|
||||
})
|
||||
|
||||
@@ -102,10 +96,11 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to search for dashboard", func() {
|
||||
Convey("Should be able to search for dashboard folder", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
Title: "test dash 23",
|
||||
OrgId: 1,
|
||||
Title: "1 test dash folder",
|
||||
OrgId: 1,
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -113,14 +108,29 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
hit := query.Result[0]
|
||||
So(len(hit.Tags), ShouldEqual, 2)
|
||||
So(hit.Type, ShouldEqual, search.DashHitFolder)
|
||||
})
|
||||
|
||||
Convey("Should be able to search for a dashboard folder's children", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
FolderIds: []int64{savedFolder.Id},
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
hit := query.Result[0]
|
||||
So(hit.Id, ShouldEqual, savedDash.Id)
|
||||
})
|
||||
|
||||
Convey("Should be able to search for dashboard by dashboard ids", func() {
|
||||
Convey("should be able to find two dashboards by id", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
DashboardIds: []int{1, 2},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{2, 3},
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -137,8 +147,8 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
Convey("DashboardIds that does not exists should not cause errors", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
DashboardIds: []int{1000},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{1000},
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -161,6 +171,63 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to update dashboard and remove folderId", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "folderId",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
Overwrite: true,
|
||||
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": 1,
|
||||
"title": "folderId",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err = SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := m.GetDashboardQuery{
|
||||
Slug: cmd.Result.Slug,
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err = GetDashboard(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to delete a dashboard folder and its children", func() {
|
||||
deleteCmd := &m.DeleteDashboardCommand{Id: savedFolder.Id}
|
||||
err := DeleteDashboard(deleteCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
FolderIds: []int64{savedFolder.Id},
|
||||
SignedInUser: &m.SignedInUser{},
|
||||
}
|
||||
|
||||
err = SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to get dashboard tags", func() {
|
||||
query := m.GetDashboardTagsQuery{OrgId: 1}
|
||||
|
||||
@@ -171,7 +238,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
|
||||
starredDash := insertTestDashboard("starred dash", 1)
|
||||
starredDash := insertTestDashboard("starred dash", 1, 0, false)
|
||||
StarDashboard(&m.StarDashboardCommand{
|
||||
DashboardId: starredDash.Id,
|
||||
UserId: 10,
|
||||
@@ -183,7 +250,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to search for starred dashboards", func() {
|
||||
query := search.FindPersistedDashboardsQuery{OrgId: 1, UserId: 10, IsStarred: true}
|
||||
query := search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: 10, OrgId: 1}, IsStarred: true}
|
||||
err := SearchDashboards(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
@@ -192,5 +259,307 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given one dashboard folder with two dashboards and one dashboard in the root folder", func() {
|
||||
folder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp")
|
||||
childDash := insertTestDashboard("test dash 23", 1, folder.Id, false, "prod", "webapp")
|
||||
insertTestDashboard("test dash 45", 1, folder.Id, false, "prod")
|
||||
|
||||
currentUser := createUser("viewer", "Viewer", false)
|
||||
|
||||
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}}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
So(query.Result[0].Id, ShouldEqual, folder.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and acl is set for dashboard folder", func() {
|
||||
var otherUser int64 = 999
|
||||
updateTestDashboardWithAcl(folder.Id, otherUser, 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}}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
|
||||
Convey("when the user is given permission", func() {
|
||||
updateTestDashboardWithAcl(folder.Id, currentUser.Id, 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}}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
So(query.Result[0].Id, ShouldEqual, folder.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("when the user is an admin", func() {
|
||||
Convey("should be able to access folder", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{
|
||||
UserId: currentUser.Id,
|
||||
OrgId: 1,
|
||||
OrgRole: m.ROLE_ADMIN,
|
||||
},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
So(query.Result[0].Id, ShouldEqual, folder.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
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}}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
|
||||
Convey("when the user is given permission to child", func() {
|
||||
updateTestDashboardWithAcl(childDash.Id, currentUser.Id, 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}}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
So(query.Result[0].Id, ShouldEqual, childDash.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("when the user is an admin", func() {
|
||||
Convey("should be able to search for child dash and folder", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{
|
||||
UserId: currentUser.Id,
|
||||
OrgId: 1,
|
||||
OrgRole: m.ROLE_ADMIN,
|
||||
},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder.Id, dashInRoot.Id, childDash.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
So(query.Result[0].Id, ShouldEqual, folder.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, childDash.Id)
|
||||
So(query.Result[2].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given two dashboard folders with one dashboard each and one dashboard in the root folder", func() {
|
||||
folder1 := insertTestDashboard("1 test dash folder", 1, 0, true, "prod")
|
||||
folder2 := insertTestDashboard("2 test dash folder", 1, 0, true, "prod")
|
||||
dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod")
|
||||
childDash1 := insertTestDashboard("child dash 1", 1, folder1.Id, false, "prod")
|
||||
childDash2 := insertTestDashboard("child dash 2", 1, folder2.Id, false, "prod")
|
||||
|
||||
currentUser := createUser("viewer", "Viewer", false)
|
||||
var rootFolderId int64 = 0
|
||||
|
||||
Convey("and one folder is expanded, the other collapsed", func() {
|
||||
Convey("should return dashboards in root and expanded folder", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{FolderIds: []int64{rootFolderId, folder1.Id}, SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 4)
|
||||
So(query.Result[0].Id, ShouldEqual, folder1.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, folder2.Id)
|
||||
So(query.Result[2].Id, ShouldEqual, childDash1.Id)
|
||||
So(query.Result[3].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and acl is set for one dashboard folder", func() {
|
||||
var otherUser int64 = 999
|
||||
updateTestDashboardWithAcl(folder1.Id, otherUser, 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)
|
||||
|
||||
Convey("should not return folder with acl or its children", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder1.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
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)
|
||||
|
||||
Convey("should return folder without acl and its children", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 4)
|
||||
So(query.Result[0].Id, ShouldEqual, folder2.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)
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
Convey("should return folder without acl but not the dashboard with acl", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
|
||||
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(query.Result[0].Id, ShouldEqual, folder2.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, childDash2.Id)
|
||||
So(query.Result[2].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a plugin with imported dashboards", func() {
|
||||
pluginId := "test-app"
|
||||
|
||||
appFolder := insertTestDashboardForPlugin("app-test", 1, 0, true, pluginId)
|
||||
insertTestDashboardForPlugin("app-dash1", 1, appFolder.Id, false, pluginId)
|
||||
insertTestDashboardForPlugin("app-dash2", 1, appFolder.Id, false, pluginId)
|
||||
|
||||
Convey("Should return imported dashboard", func() {
|
||||
query := m.GetDashboardsByPluginIdQuery{
|
||||
PluginId: pluginId,
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err := GetDashboardsByPluginId(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func insertTestDashboard(title string, orgId int64, folderId int64, isFolder bool, tags ...interface{}) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
IsFolder: isFolder,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
"tags": tags,
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return cmd.Result
|
||||
}
|
||||
|
||||
func insertTestDashboardForPlugin(title string, orgId int64, folderId int64, isFolder bool, pluginId string) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
IsFolder: isFolder,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
}),
|
||||
PluginId: pluginId,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return cmd.Result
|
||||
}
|
||||
|
||||
func createUser(name string, role string, isAdmin bool) m.User {
|
||||
setting.AutoAssignOrg = true
|
||||
setting.AutoAssignOrgRole = role
|
||||
|
||||
currentUserCmd := m.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin}
|
||||
err := CreateUser(¤tUserCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := m.GetUserOrgListQuery{UserId: currentUserCmd.Result.Id}
|
||||
GetUserOrgList(&q1)
|
||||
So(q1.Result[0].Role, ShouldEqual, role)
|
||||
|
||||
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,
|
||||
FolderId: newFolderId,
|
||||
Dashboard: dashboard,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return cmd.Result
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ func GetDashboardVersion(query *m.GetDashboardVersionQuery) error {
|
||||
|
||||
// GetDashboardVersions gets all dashboard versions for the given dashboard ID.
|
||||
func GetDashboardVersions(query *m.GetDashboardVersionsQuery) error {
|
||||
if query.Limit == 0 {
|
||||
query.Limit = 1000
|
||||
}
|
||||
|
||||
err := x.Table("dashboard_version").
|
||||
Select(`dashboard_version.id,
|
||||
dashboard_version.dashboard_id,
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestGetDashboardVersion(t *testing.T) {
|
||||
InitTestDB(t)
|
||||
|
||||
Convey("Get a Dashboard ID and version ID", func() {
|
||||
savedDash := insertTestDashboard("test dash 26", 1, "diff")
|
||||
savedDash := insertTestDashboard("test dash 26", 1, 0, false, "diff")
|
||||
|
||||
query := m.GetDashboardVersionQuery{
|
||||
DashboardId: savedDash.Id,
|
||||
@@ -70,7 +70,7 @@ func TestGetDashboardVersion(t *testing.T) {
|
||||
func TestGetDashboardVersions(t *testing.T) {
|
||||
Convey("Testing dashboard versions retrieval", t, func() {
|
||||
InitTestDB(t)
|
||||
savedDash := insertTestDashboard("test dash 43", 1, "diff-all")
|
||||
savedDash := insertTestDashboard("test dash 43", 1, 0, false, "diff-all")
|
||||
|
||||
Convey("Get all versions for a given Dashboard ID", func() {
|
||||
query := m.GetDashboardVersionsQuery{DashboardId: savedDash.Id, OrgId: 1}
|
||||
@@ -110,7 +110,7 @@ func TestDeleteExpiredVersions(t *testing.T) {
|
||||
versionsToWrite := 10
|
||||
setting.DashboardVersionsToKeep = versionsToKeep
|
||||
|
||||
savedDash := insertTestDashboard("test dash 53", 1, "diff-all")
|
||||
savedDash := insertTestDashboard("test dash 53", 1, 0, false, "diff-all")
|
||||
for i := 0; i < versionsToWrite-1; i++ {
|
||||
updateTestDashboard(savedDash, map[string]interface{}{
|
||||
"tags": "different-tag",
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
|
||||
)
|
||||
|
||||
func InitTestDB(t *testing.T) {
|
||||
func InitTestDB(t *testing.T) *xorm.Engine {
|
||||
x, err := xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
|
||||
//x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
|
||||
//x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
|
||||
@@ -27,6 +27,8 @@ func InitTestDB(t *testing.T) {
|
||||
if err := SetEngine(x); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package migrations
|
||||
|
||||
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func addDashboardAclMigrations(mg *Migrator) {
|
||||
dashboardAclV1 := Table{
|
||||
Name: "dashboard_acl",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: DB_BigInt},
|
||||
{Name: "dashboard_id", Type: DB_BigInt},
|
||||
{Name: "user_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "team_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "permission", Type: DB_SmallInt, Default: "4"},
|
||||
{Name: "role", Type: DB_Varchar, Length: 20, Nullable: true},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"dashboard_id"}},
|
||||
{Cols: []string{"dashboard_id", "user_id"}, Type: UniqueIndex},
|
||||
{Cols: []string{"dashboard_id", "team_id"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create dashboard acl table", NewAddTableMigration(dashboardAclV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index dashboard_acl_dashboard_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[0]))
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id_user_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[1]))
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id_team_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[2]))
|
||||
|
||||
const rawSQL = `
|
||||
INSERT INTO dashboard_acl
|
||||
(
|
||||
org_id,
|
||||
dashboard_id,
|
||||
permission,
|
||||
role,
|
||||
created,
|
||||
updated
|
||||
)
|
||||
VALUES
|
||||
(-1,-1, 1,'Viewer','2017-06-20','2017-06-20'),
|
||||
(-1,-1, 2,'Editor','2017-06-20','2017-06-20')
|
||||
`
|
||||
|
||||
mg.AddMigration("save default acl rules in dashboard_acl table", new(RawSqlMigration).
|
||||
Sqlite(rawSQL).
|
||||
Postgres(rawSQL).
|
||||
Mysql(rawSQL))
|
||||
}
|
||||
@@ -136,4 +136,18 @@ func addDashboardMigration(mg *Migrator) {
|
||||
mg.AddMigration("Update dashboard_tag table charset", NewTableCharsetMigration("dashboard_tag", []*Column{
|
||||
{Name: "term", Type: DB_NVarchar, Length: 50, Nullable: false},
|
||||
}))
|
||||
|
||||
// add column to store folder_id for dashboard folder structure
|
||||
mg.AddMigration("Add column folder_id in dashboard", NewAddColumnMigration(dashboardV2, &Column{
|
||||
Name: "folder_id", Type: DB_BigInt, Nullable: false, Default: "0",
|
||||
}))
|
||||
|
||||
mg.AddMigration("Add column isFolder in dashboard", NewAddColumnMigration(dashboardV2, &Column{
|
||||
Name: "is_folder", Type: DB_Bool, Nullable: false, Default: "0",
|
||||
}))
|
||||
|
||||
// add column to flag if dashboard has an ACL
|
||||
mg.AddMigration("Add column has_acl in dashboard", NewAddColumnMigration(dashboardV2, &Column{
|
||||
Name: "has_acl", Type: DB_Bool, Nullable: false, Default: "0",
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ func AddMigrations(mg *Migrator) {
|
||||
addAnnotationMig(mg)
|
||||
addTestDataMigrations(mg)
|
||||
addDashboardVersionMigration(mg)
|
||||
addTeamMigrations(mg)
|
||||
addDashboardAclMigrations(mg)
|
||||
addTagMigration(mg)
|
||||
}
|
||||
|
||||
|
||||
@@ -83,4 +83,10 @@ func addOrgMigrations(mg *Migrator) {
|
||||
mg.AddMigration("Update org_user table charset", NewTableCharsetMigration("org_user", []*Column{
|
||||
{Name: "role", Type: DB_NVarchar, Length: 20},
|
||||
}))
|
||||
|
||||
const migrateReadOnlyViewersToViewers = `UPDATE org_user SET role = 'Viewer' WHERE role = 'Read Only Editor'`
|
||||
mg.AddMigration("Migrate all Read Only Viewers to Viewers", new(RawSqlMigration).
|
||||
Sqlite(migrateReadOnlyViewersToViewers).
|
||||
Postgres(migrateReadOnlyViewersToViewers).
|
||||
Mysql(migrateReadOnlyViewersToViewers))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package migrations
|
||||
|
||||
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func addTeamMigrations(mg *Migrator) {
|
||||
teamV1 := Table{
|
||||
Name: "team",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
|
||||
{Name: "org_id", Type: DB_BigInt},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"org_id", "name"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create team table", NewAddTableMigration(teamV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index team.org_id", NewAddIndexMigration(teamV1, teamV1.Indices[0]))
|
||||
mg.AddMigration("add unique index team_org_id_name", NewAddIndexMigration(teamV1, teamV1.Indices[1]))
|
||||
|
||||
teamMemberV1 := Table{
|
||||
Name: "team_member",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: DB_BigInt},
|
||||
{Name: "team_id", Type: DB_BigInt},
|
||||
{Name: "user_id", Type: DB_BigInt},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"org_id", "team_id", "user_id"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create team member table", NewAddTableMigration(teamMemberV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index team_member.org_id", NewAddIndexMigration(teamMemberV1, teamMemberV1.Indices[0]))
|
||||
mg.AddMigration("add unique index team_member_org_id_team_id_user_id", NewAddIndexMigration(teamMemberV1, teamMemberV1.Indices[1]))
|
||||
|
||||
// add column email
|
||||
mg.AddMigration("Add column email to team table", NewAddColumnMigration(teamV1, &Column{
|
||||
Name: "email", Type: DB_NVarchar, Nullable: true, Length: 190,
|
||||
}))
|
||||
}
|
||||
@@ -154,6 +154,57 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
So(err, ShouldEqual, m.ErrLastOrgAdmin)
|
||||
})
|
||||
|
||||
Convey("Given an org user with dashboard permissions", func() {
|
||||
ac3cmd := m.CreateUserCommand{Login: "ac3", Email: "ac3@test.com", Name: "ac3 name", IsAdmin: false}
|
||||
err := CreateUser(&ac3cmd)
|
||||
So(err, ShouldBeNil)
|
||||
ac3 := ac3cmd.Result
|
||||
|
||||
orgUserCmd := m.AddOrgUserCommand{
|
||||
OrgId: ac1.OrgId,
|
||||
UserId: ac3.Id,
|
||||
Role: m.ROLE_VIEWER,
|
||||
}
|
||||
|
||||
err = AddOrgUser(&orgUserCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := m.GetOrgUsersQuery{OrgId: ac1.OrgId}
|
||||
err = GetOrgUsers(&query)
|
||||
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})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When org user is deleted", func() {
|
||||
cmdRemove := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac3.Id}
|
||||
err := RemoveOrgUser(&cmdRemove)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should remove dependent permissions for deleted org user", func() {
|
||||
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: ac1.OrgId}
|
||||
err = GetDashboardAclInfoList(permQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(permQuery.Result), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should not remove dashboard permissions for same user in another org", func() {
|
||||
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 2, OrgId: ac3.OrgId}
|
||||
err = GetDashboardAclInfoList(permQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(permQuery.Result), ShouldEqual, 1)
|
||||
So(permQuery.Result[0].OrgId, ShouldEqual, ac3.OrgId)
|
||||
So(permQuery.Result[0].UserId, ShouldEqual, ac3.Id)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -88,10 +88,17 @@ func GetOrgUsers(query *m.GetOrgUsersQuery) error {
|
||||
|
||||
func RemoveOrgUser(cmd *m.RemoveOrgUserCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
var rawSql = "DELETE FROM org_user WHERE org_id=? and user_id=?"
|
||||
_, err := sess.Exec(rawSql, cmd.OrgId, cmd.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
deletes := []string{
|
||||
"DELETE FROM org_user WHERE org_id=? and user_id=?",
|
||||
"DELETE FROM dashboard_acl WHERE org_id=? and user_id = ?",
|
||||
"DELETE FROM team_member WHERE org_id=? and user_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
_, err := sess.Exec(sql, cmd.OrgId, cmd.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return validateOneAdminLeftInOrg(cmd.OrgId, sess)
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// SearchBuilder is a builder/object mother that builds a dashboard search query
|
||||
type SearchBuilder struct {
|
||||
tags []string
|
||||
isStarred bool
|
||||
limit int
|
||||
signedInUser *m.SignedInUser
|
||||
whereDashboardIdsIn []int64
|
||||
whereTitle string
|
||||
whereTypeFolder bool
|
||||
whereTypeDash bool
|
||||
whereFolderIds []int64
|
||||
sql bytes.Buffer
|
||||
params []interface{}
|
||||
}
|
||||
|
||||
func NewSearchBuilder(signedInUser *m.SignedInUser, limit int) *SearchBuilder {
|
||||
searchBuilder := &SearchBuilder{
|
||||
signedInUser: signedInUser,
|
||||
limit: limit,
|
||||
}
|
||||
|
||||
return searchBuilder
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) WithTags(tags []string) *SearchBuilder {
|
||||
if len(tags) > 0 {
|
||||
sb.tags = tags
|
||||
}
|
||||
|
||||
return sb
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) IsStarred() *SearchBuilder {
|
||||
sb.isStarred = true
|
||||
|
||||
return sb
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) WithDashboardIdsIn(ids []int64) *SearchBuilder {
|
||||
if len(ids) > 0 {
|
||||
sb.whereDashboardIdsIn = ids
|
||||
}
|
||||
|
||||
return sb
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) WithTitle(title string) *SearchBuilder {
|
||||
sb.whereTitle = title
|
||||
|
||||
return sb
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) WithType(queryType string) *SearchBuilder {
|
||||
if len(queryType) > 0 && queryType == "dash-folder" {
|
||||
sb.whereTypeFolder = true
|
||||
}
|
||||
|
||||
if len(queryType) > 0 && queryType == "dash-db" {
|
||||
sb.whereTypeDash = true
|
||||
}
|
||||
|
||||
return sb
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) WithFolderIds(folderIds []int64) *SearchBuilder {
|
||||
sb.whereFolderIds = folderIds
|
||||
return sb
|
||||
}
|
||||
|
||||
// ToSql builds the sql and returns it as a string, together with the params.
|
||||
func (sb *SearchBuilder) ToSql() (string, []interface{}) {
|
||||
sb.params = make([]interface{}, 0)
|
||||
|
||||
sb.buildSelect()
|
||||
|
||||
if len(sb.tags) > 0 {
|
||||
sb.buildTagQuery()
|
||||
} else {
|
||||
sb.buildMainQuery()
|
||||
}
|
||||
|
||||
sb.sql.WriteString(`
|
||||
LEFT OUTER JOIN dashboard folder on folder.id = dashboard.folder_id
|
||||
LEFT OUTER JOIN dashboard_tag on dashboard.id = dashboard_tag.dashboard_id`)
|
||||
|
||||
sb.sql.WriteString(" ORDER BY dashboard.title ASC LIMIT 5000")
|
||||
|
||||
return sb.sql.String(), sb.params
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) buildSelect() {
|
||||
sb.sql.WriteString(
|
||||
`SELECT
|
||||
dashboard.id,
|
||||
dashboard.title,
|
||||
dashboard.slug,
|
||||
dashboard_tag.term,
|
||||
dashboard.is_folder,
|
||||
dashboard.folder_id,
|
||||
folder.slug as folder_slug,
|
||||
folder.title as folder_title
|
||||
FROM `)
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) buildTagQuery() {
|
||||
sb.sql.WriteString(
|
||||
`(
|
||||
SELECT
|
||||
dashboard.id FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_tag ON dashboard_tag.dashboard_id = dashboard.id
|
||||
`)
|
||||
|
||||
if sb.isStarred {
|
||||
sb.sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
|
||||
}
|
||||
|
||||
sb.sql.WriteString(` WHERE dashboard_tag.term IN (?` + strings.Repeat(",?", len(sb.tags)-1) + `) AND `)
|
||||
for _, tag := range sb.tags {
|
||||
sb.params = append(sb.params, tag)
|
||||
}
|
||||
|
||||
sb.buildSearchWhereClause()
|
||||
|
||||
// this ends the inner select (tag filtered part)
|
||||
sb.sql.WriteString(`
|
||||
GROUP BY dashboard.id HAVING COUNT(dashboard.id) >= ?
|
||||
LIMIT ?) as ids
|
||||
INNER JOIN dashboard on ids.id = dashboard.id
|
||||
`)
|
||||
|
||||
sb.params = append(sb.params, len(sb.tags))
|
||||
sb.params = append(sb.params, sb.limit)
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) buildMainQuery() {
|
||||
sb.sql.WriteString(`( SELECT dashboard.id FROM dashboard `)
|
||||
|
||||
if sb.isStarred {
|
||||
sb.sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
|
||||
}
|
||||
|
||||
sb.sql.WriteString(` WHERE `)
|
||||
sb.buildSearchWhereClause()
|
||||
|
||||
sb.sql.WriteString(`
|
||||
LIMIT ?) as ids
|
||||
INNER JOIN dashboard on ids.id = dashboard.id
|
||||
`)
|
||||
sb.params = append(sb.params, sb.limit)
|
||||
}
|
||||
|
||||
func (sb *SearchBuilder) buildSearchWhereClause() {
|
||||
sb.sql.WriteString(` dashboard.org_id=?`)
|
||||
sb.params = append(sb.params, sb.signedInUser.OrgId)
|
||||
|
||||
if sb.isStarred {
|
||||
sb.sql.WriteString(` AND star.user_id=?`)
|
||||
sb.params = append(sb.params, sb.signedInUser.UserId)
|
||||
}
|
||||
|
||||
if len(sb.whereDashboardIdsIn) > 0 {
|
||||
sb.sql.WriteString(` AND dashboard.id IN (?` + strings.Repeat(",?", len(sb.whereDashboardIdsIn)-1) + `)`)
|
||||
for _, dashboardId := range sb.whereDashboardIdsIn {
|
||||
sb.params = append(sb.params, dashboardId)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if len(sb.whereTitle) > 0 {
|
||||
sb.sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?")
|
||||
sb.params = append(sb.params, "%"+sb.whereTitle+"%")
|
||||
}
|
||||
|
||||
if sb.whereTypeFolder {
|
||||
sb.sql.WriteString(" AND dashboard.is_folder = " + dialect.BooleanStr(true))
|
||||
}
|
||||
|
||||
if sb.whereTypeDash {
|
||||
sb.sql.WriteString(" AND dashboard.is_folder = " + dialect.BooleanStr(false))
|
||||
}
|
||||
|
||||
if len(sb.whereFolderIds) > 0 {
|
||||
sb.sql.WriteString(` AND dashboard.folder_id IN (?` + strings.Repeat(",?", len(sb.whereFolderIds)-1) + `) `)
|
||||
for _, id := range sb.whereFolderIds {
|
||||
sb.params = append(sb.params, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestSearchBuilder(t *testing.T) {
|
||||
dialect = migrator.NewDialect("sqlite3")
|
||||
|
||||
Convey("Testing building a search", t, func() {
|
||||
signedInUser := &m.SignedInUser{
|
||||
OrgId: 1,
|
||||
UserId: 1,
|
||||
}
|
||||
sb := NewSearchBuilder(signedInUser, 1000)
|
||||
|
||||
Convey("When building a normal search", func() {
|
||||
sql, params := sb.IsStarred().WithTitle("test").ToSql()
|
||||
So(sql, ShouldStartWith, "SELECT")
|
||||
So(sql, ShouldContainSubstring, "INNER JOIN dashboard on ids.id = dashboard.id")
|
||||
So(sql, ShouldEndWith, "ORDER BY dashboard.title ASC LIMIT 5000")
|
||||
So(len(params), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
|
||||
Convey("When building a search with tag filter", func() {
|
||||
sql, params := sb.WithTags([]string{"tag1", "tag2"}).ToSql()
|
||||
So(sql, ShouldStartWith, "SELECT")
|
||||
So(sql, ShouldContainSubstring, "LEFT OUTER JOIN dashboard_tag")
|
||||
So(sql, ShouldEndWith, "ORDER BY dashboard.title ASC LIMIT 5000")
|
||||
So(len(params), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"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/setting"
|
||||
@@ -102,6 +103,7 @@ func SetEngine(engine *xorm.Engine) (err error) {
|
||||
|
||||
// Init repo instances
|
||||
annotations.SetRepository(&SqlAnnotationRepo{})
|
||||
dashboards.SetRepository(&dashboards.DashboardRepository{})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", CreateTeam)
|
||||
bus.AddHandler("sql", UpdateTeam)
|
||||
bus.AddHandler("sql", DeleteTeam)
|
||||
bus.AddHandler("sql", SearchTeams)
|
||||
bus.AddHandler("sql", GetTeamById)
|
||||
bus.AddHandler("sql", GetTeamsByUser)
|
||||
|
||||
bus.AddHandler("sql", AddTeamMember)
|
||||
bus.AddHandler("sql", RemoveTeamMember)
|
||||
bus.AddHandler("sql", GetTeamMembers)
|
||||
}
|
||||
|
||||
func CreateTeam(cmd *m.CreateTeamCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
|
||||
if isNameTaken, err := isTeamNameTaken(cmd.Name, 0, sess); err != nil {
|
||||
return err
|
||||
} else if isNameTaken {
|
||||
return m.ErrTeamNameTaken
|
||||
}
|
||||
|
||||
team := m.Team{
|
||||
Name: cmd.Name,
|
||||
Email: cmd.Email,
|
||||
OrgId: cmd.OrgId,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
_, err := sess.Insert(&team)
|
||||
|
||||
cmd.Result = team
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateTeam(cmd *m.UpdateTeamCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
|
||||
if isNameTaken, err := isTeamNameTaken(cmd.Name, cmd.Id, sess); err != nil {
|
||||
return err
|
||||
} else if isNameTaken {
|
||||
return m.ErrTeamNameTaken
|
||||
}
|
||||
|
||||
team := m.Team{
|
||||
Name: cmd.Name,
|
||||
Email: cmd.Email,
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
sess.MustCols("email")
|
||||
|
||||
affectedRows, err := sess.Id(cmd.Id).Update(&team)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if affectedRows == 0 {
|
||||
return m.ErrTeamNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteTeam(cmd *m.DeleteTeamCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if res, err := sess.Query("SELECT 1 from team WHERE id=?", cmd.Id); err != nil {
|
||||
return err
|
||||
} else if len(res) != 1 {
|
||||
return m.ErrTeamNotFound
|
||||
}
|
||||
|
||||
deletes := []string{
|
||||
"DELETE FROM team_member WHERE team_id = ?",
|
||||
"DELETE FROM team WHERE id = ?",
|
||||
"DELETE FROM dashboard_acl WHERE team_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
_, err := sess.Exec(sql, cmd.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func isTeamNameTaken(name string, existingId int64, sess *DBSession) (bool, error) {
|
||||
var team m.Team
|
||||
exists, err := sess.Where("name=?", name).Get(&team)
|
||||
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if exists && existingId != team.Id {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func SearchTeams(query *m.SearchTeamsQuery) error {
|
||||
query.Result = m.SearchTeamQueryResult{
|
||||
Teams: make([]*m.SearchTeamDto, 0),
|
||||
}
|
||||
queryWithWildcards := "%" + query.Query + "%"
|
||||
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
sql.WriteString(`select
|
||||
team.id as id,
|
||||
team.name as name,
|
||||
team.email as email,
|
||||
(select count(*) from team_member where team_member.team_id = team.id) as member_count
|
||||
from team as team
|
||||
where team.org_id = ?`)
|
||||
|
||||
params = append(params, query.OrgId)
|
||||
|
||||
if query.Query != "" {
|
||||
sql.WriteString(` and team.name ` + dialect.LikeStr() + ` ?`)
|
||||
params = append(params, queryWithWildcards)
|
||||
}
|
||||
|
||||
if query.Name != "" {
|
||||
sql.WriteString(` and team.name = ?`)
|
||||
params = append(params, query.Name)
|
||||
}
|
||||
|
||||
sql.WriteString(` order by team.name asc`)
|
||||
|
||||
if query.Limit != 0 {
|
||||
sql.WriteString(` limit ? offset ?`)
|
||||
offset := query.Limit * (query.Page - 1)
|
||||
params = append(params, query.Limit, offset)
|
||||
}
|
||||
|
||||
if err := x.Sql(sql.String(), params...).Find(&query.Result.Teams); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
team := m.Team{}
|
||||
countSess := x.Table("team")
|
||||
if query.Query != "" {
|
||||
countSess.Where(`name `+dialect.LikeStr()+` ?`, queryWithWildcards)
|
||||
}
|
||||
|
||||
if query.Name != "" {
|
||||
countSess.Where("name=?", query.Name)
|
||||
}
|
||||
|
||||
count, err := countSess.Count(&team)
|
||||
query.Result.TotalCount = count
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetTeamById(query *m.GetTeamByIdQuery) error {
|
||||
var team m.Team
|
||||
exists, err := x.Id(query.Id).Get(&team)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return m.ErrTeamNotFound
|
||||
}
|
||||
|
||||
query.Result = &team
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
|
||||
query.Result = make([]*m.Team, 0)
|
||||
|
||||
sess := x.Table("team")
|
||||
sess.Join("INNER", "team_member", "team.id=team_member.team_id")
|
||||
sess.Where("team_member.user_id=?", query.UserId)
|
||||
|
||||
err := sess.Find(&query.Result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if res, err := sess.Query("SELECT 1 from team_member WHERE team_id=? and user_id=?", cmd.TeamId, cmd.UserId); err != nil {
|
||||
return err
|
||||
} else if len(res) == 1 {
|
||||
return m.ErrTeamMemberAlreadyAdded
|
||||
}
|
||||
|
||||
if res, err := sess.Query("SELECT 1 from team WHERE id=?", cmd.TeamId); err != nil {
|
||||
return err
|
||||
} else if len(res) != 1 {
|
||||
return m.ErrTeamNotFound
|
||||
}
|
||||
|
||||
entity := m.TeamMember{
|
||||
OrgId: cmd.OrgId,
|
||||
TeamId: cmd.TeamId,
|
||||
UserId: cmd.UserId,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
_, err := sess.Insert(&entity)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
var rawSql = "DELETE FROM team_member WHERE team_id=? and user_id=?"
|
||||
_, err := sess.Exec(rawSql, cmd.TeamId, cmd.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func GetTeamMembers(query *m.GetTeamMembersQuery) error {
|
||||
query.Result = make([]*m.TeamMemberDTO, 0)
|
||||
sess := x.Table("team_member")
|
||||
sess.Join("INNER", "user", fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user")))
|
||||
sess.Where("team_member.team_id=?", query.TeamId)
|
||||
sess.Cols("user.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login")
|
||||
sess.Asc("user.login", "user.email")
|
||||
|
||||
err := sess.Find(&query.Result)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func TestTeamCommandsAndQueries(t *testing.T) {
|
||||
|
||||
Convey("Testing Team commands & queries", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
Convey("Given saved users and two teams", func() {
|
||||
var userIds []int64
|
||||
for i := 0; i < 5; i++ {
|
||||
userCmd := &m.CreateUserCommand{
|
||||
Email: fmt.Sprint("user", i, "@test.com"),
|
||||
Name: fmt.Sprint("user", i),
|
||||
Login: fmt.Sprint("loginuser", i),
|
||||
}
|
||||
err := CreateUser(userCmd)
|
||||
So(err, ShouldBeNil)
|
||||
userIds = append(userIds, userCmd.Result.Id)
|
||||
}
|
||||
|
||||
group1 := m.CreateTeamCommand{Name: "group1 name", Email: "test1@test.com"}
|
||||
group2 := m.CreateTeamCommand{Name: "group2 name", Email: "test2@test.com"}
|
||||
|
||||
err := CreateTeam(&group1)
|
||||
So(err, ShouldBeNil)
|
||||
err = CreateTeam(&group2)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should be able to create teams and add users", func() {
|
||||
query := &m.SearchTeamsQuery{Name: "group1 name", Page: 1, Limit: 10}
|
||||
err = SearchTeams(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Page, ShouldEqual, 1)
|
||||
|
||||
team1 := query.Result.Teams[0]
|
||||
So(team1.Name, ShouldEqual, "group1 name")
|
||||
So(team1.Email, ShouldEqual, "test1@test.com")
|
||||
|
||||
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: team1.Id, UserId: userIds[0]})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := &m.GetTeamMembersQuery{TeamId: team1.Id}
|
||||
err = GetTeamMembers(q1)
|
||||
So(err, ShouldBeNil)
|
||||
So(q1.Result[0].TeamId, ShouldEqual, team1.Id)
|
||||
So(q1.Result[0].Login, ShouldEqual, "loginuser0")
|
||||
})
|
||||
|
||||
Convey("Should be able to search for teams", func() {
|
||||
query := &m.SearchTeamsQuery{Query: "group", Page: 1}
|
||||
err = SearchTeams(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Teams), ShouldEqual, 2)
|
||||
So(query.Result.TotalCount, ShouldEqual, 2)
|
||||
|
||||
query2 := &m.SearchTeamsQuery{Query: ""}
|
||||
err = SearchTeams(query2)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query2.Result.Teams), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Should be able to return all teams a user is member of", func() {
|
||||
groupId := group2.Result.Id
|
||||
err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: groupId, UserId: userIds[0]})
|
||||
|
||||
query := &m.GetTeamsByUserQuery{UserId: userIds[0]}
|
||||
err = GetTeamsByUser(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
So(query.Result[0].Name, ShouldEqual, "group2 name")
|
||||
So(query.Result[0].Email, ShouldEqual, "test2@test.com")
|
||||
})
|
||||
|
||||
Convey("Should be able to remove users from a group", func() {
|
||||
err = RemoveTeamMember(&m.RemoveTeamMemberCommand{TeamId: group1.Result.Id, UserId: userIds[0]})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := &m.GetTeamMembersQuery{TeamId: group1.Result.Id}
|
||||
err = GetTeamMembers(q1)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q1.Result), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to remove a group with users and permissions", func() {
|
||||
groupId := group2.Result.Id
|
||||
err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: groupId, UserId: userIds[1]})
|
||||
So(err, ShouldBeNil)
|
||||
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: groupId, UserId: userIds[2]})
|
||||
So(err, ShouldBeNil)
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: 1, Permission: m.PERMISSION_EDIT, TeamId: groupId})
|
||||
|
||||
err = DeleteTeam(&m.DeleteTeamCommand{Id: groupId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := &m.GetTeamByIdQuery{Id: groupId}
|
||||
err = GetTeamById(query)
|
||||
So(err, ShouldEqual, m.ErrTeamNotFound)
|
||||
|
||||
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(permQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(permQuery.Result), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -350,6 +350,7 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
|
||||
u.name as name,
|
||||
u.help_flags1 as help_flags1,
|
||||
u.last_seen_at as last_seen_at,
|
||||
(SELECT COUNT(*) FROM org_user where org_user.user_id = u.id) as org_count,
|
||||
org.name as org_name,
|
||||
org_user.role as org_role,
|
||||
org.id as org_id
|
||||
@@ -400,7 +401,7 @@ func SearchUsers(query *m.SearchUsersQuery) error {
|
||||
}
|
||||
|
||||
if query.Query != "" {
|
||||
whereConditions = append(whereConditions, "(email LIKE ? OR name LIKE ? OR login like ?)")
|
||||
whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
|
||||
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
|
||||
}
|
||||
|
||||
@@ -438,6 +439,10 @@ func DeleteUser(cmd *m.DeleteUserCommand) error {
|
||||
deletes := []string{
|
||||
"DELETE FROM star WHERE user_id = ?",
|
||||
"DELETE FROM " + dialect.Quote("user") + " WHERE id = ?",
|
||||
"DELETE FROM org_user WHERE user_id = ?",
|
||||
"DELETE FROM dashboard_acl WHERE user_id = ?",
|
||||
"DELETE FROM preferences WHERE user_id = ?",
|
||||
"DELETE FROM team_member WHERE user_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func TestUserDataAccess(t *testing.T) {
|
||||
@@ -14,80 +14,134 @@ func TestUserDataAccess(t *testing.T) {
|
||||
Convey("Testing DB", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
var err error
|
||||
for i := 0; i < 5; i++ {
|
||||
err = CreateUser(&models.CreateUserCommand{
|
||||
Email: fmt.Sprint("user", i, "@test.com"),
|
||||
Name: fmt.Sprint("user", i),
|
||||
Login: fmt.Sprint("loginuser", i),
|
||||
Convey("Given 5 users", func() {
|
||||
var err error
|
||||
var cmd *m.CreateUserCommand
|
||||
users := []m.User{}
|
||||
for i := 0; i < 5; i++ {
|
||||
cmd = &m.CreateUserCommand{
|
||||
Email: fmt.Sprint("user", i, "@test.com"),
|
||||
Name: fmt.Sprint("user", i),
|
||||
Login: fmt.Sprint("loginuser", i),
|
||||
}
|
||||
err = CreateUser(cmd)
|
||||
So(err, ShouldBeNil)
|
||||
users = append(users, cmd.Result)
|
||||
}
|
||||
|
||||
Convey("Can return the first page of users and a total count", func() {
|
||||
query := m.SearchUsersQuery{Query: "", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 3)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
Convey("Can return the first page of users and a total count", func() {
|
||||
query := models.SearchUsersQuery{Query: "", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("Can return the second page of users and a total count", func() {
|
||||
query := m.SearchUsersQuery{Query: "", Page: 2, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 3)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 2)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
})
|
||||
|
||||
Convey("Can return the second page of users and a total count", func() {
|
||||
query := models.SearchUsersQuery{Query: "", Page: 2, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("Can return list of users matching query on user name", func() {
|
||||
query := m.SearchUsersQuery{Query: "use", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 2)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 3)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
|
||||
Convey("Can return list of users matching query on user name", func() {
|
||||
query := models.SearchUsersQuery{Query: "use", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
query = m.SearchUsersQuery{Query: "ser1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 3)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
|
||||
query = models.SearchUsersQuery{Query: "ser1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
query = m.SearchUsersQuery{Query: "USER1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
|
||||
query = models.SearchUsersQuery{Query: "USER1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
query = m.SearchUsersQuery{Query: "idontexist", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 0)
|
||||
So(query.Result.TotalCount, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
query = models.SearchUsersQuery{Query: "idontexist", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("Can return list of users matching query on email", func() {
|
||||
query := m.SearchUsersQuery{Query: "ser1@test.com", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 0)
|
||||
So(query.Result.TotalCount, ShouldEqual, 0)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Can return list of users matching query on email", func() {
|
||||
query := models.SearchUsersQuery{Query: "ser1@test.com", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("Can return list of users matching query on login name", func() {
|
||||
query := m.SearchUsersQuery{Query: "loginuser1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Can return list of users matching query on login name", func() {
|
||||
query := models.SearchUsersQuery{Query: "loginuser1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("when a user is an org member and has been assigned permissions", func() {
|
||||
err = AddOrgUser(&m.AddOrgUserCommand{LoginOrEmail: users[0].Login, Role: m.ROLE_VIEWER, OrgId: users[0].OrgId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{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"})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("when the user is deleted", func() {
|
||||
err = DeleteUser(&m.DeleteUserCommand{UserId: users[0].Id})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should delete connected org users and permissions", func() {
|
||||
query := &m.GetOrgUsersQuery{OrgId: 1}
|
||||
err = GetOrgUsersForTest(query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
|
||||
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(permQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(permQuery.Result), ShouldEqual, 0)
|
||||
|
||||
prefsQuery := &m.GetPreferencesQuery{OrgId: users[0].OrgId, UserId: users[0].Id}
|
||||
err = GetPreferences(prefsQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(prefsQuery.Result.OrgId, ShouldEqual, 0)
|
||||
So(prefsQuery.Result.UserId, ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func GetOrgUsersForTest(query *m.GetOrgUsersQuery) error {
|
||||
query.Result = make([]*m.OrgUserDTO, 0)
|
||||
sess := x.Table("org_user")
|
||||
sess.Join("LEFT ", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
|
||||
sess.Where("org_user.org_id=?", query.OrgId)
|
||||
sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role")
|
||||
|
||||
err := sess.Find(&query.Result)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user