Commit 7b17d134 by Torkel Ödegaard

Merge branch 'develop' of github.com:grafana/grafana into develop

parents 0e44fe08 7c741111
...@@ -54,15 +54,16 @@ type Query struct { ...@@ -54,15 +54,16 @@ type Query struct {
} }
type FindPersistedDashboardsQuery struct { type FindPersistedDashboardsQuery struct {
Title string Title string
OrgId int64 OrgId int64
SignedInUser *models.SignedInUser SignedInUser *models.SignedInUser
IsStarred bool IsStarred bool
DashboardIds []int64 DashboardIds []int64
Type string Type string
FolderId int64 FolderId int64
Tags []string Tags []string
Limit int ExpandedFolders []int64
Limit int
Result HitList Result HitList
} }
package sqlstore package sqlstore
import ( import (
"bytes"
"fmt"
"strings"
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
...@@ -189,137 +186,44 @@ type DashboardSearchProjection struct { ...@@ -189,137 +186,44 @@ type DashboardSearchProjection struct {
} }
func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
var sql bytes.Buffer
params := make([]interface{}, 0)
limit := query.Limit limit := query.Limit
if limit == 0 { if limit == 0 {
limit = 1000 limit = 1000
} }
sql.WriteString(` sb := NewSearchBuilder(query.SignedInUser, limit).
SELECT WithTags(query.Tags).
dashboard.id, WithDashboardIdsIn(query.DashboardIds)
dashboard.title,
dashboard.slug,
dashboard_tag.term,
dashboard.is_folder,
dashboard.folder_id,
folder.slug as folder_slug,
folder.title as folder_title
FROM `)
// add tags filter
if len(query.Tags) > 0 {
sql.WriteString(
`(
SELECT
dashboard.id 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")
}
sql.WriteString(` WHERE dashboard_tag.term IN (?` + strings.Repeat(",?", len(query.Tags)-1) + `) AND `)
for _, tag := range query.Tags {
params = append(params, tag)
}
params = createSearchWhereClause(query, &sql, params)
fmt.Printf("params2 %v", params)
// this ends the inner select (tag filtered part)
sql.WriteString(`
GROUP BY dashboard.id HAVING COUNT(dashboard.id) >= ?
LIMIT ?) as ids
INNER JOIN dashboard on ids.id = dashboard.id
`)
params = append(params, len(query.Tags))
params = append(params, limit)
} else {
sql.WriteString(`( SELECT dashboard.id FROM dashboard `)
if query.IsStarred {
sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
}
sql.WriteString(` WHERE `)
params = createSearchWhereClause(query, &sql, params)
sql.WriteString(`
LIMIT ?) as ids
INNER JOIN dashboard on ids.id = dashboard.id
`)
params = append(params, limit)
}
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`)
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 5000"))
var res []DashboardSearchProjection
err := x.Sql(sql.String(), params...).Find(&res)
if err != nil {
return nil, err
}
return res, nil
}
func createSearchWhereClause(query *search.FindPersistedDashboardsQuery, sql *bytes.Buffer, params []interface{}) []interface{} {
sql.WriteString(` dashboard.org_id=?`)
params = append(params, query.SignedInUser.OrgId)
if query.IsStarred { if query.IsStarred {
sql.WriteString(` AND star.user_id=?`) sb.IsStarred()
params = append(params, query.SignedInUser.UserId)
} }
if len(query.DashboardIds) > 0 { if len(query.Title) > 0 {
sql.WriteString(` AND dashboard.id IN (?` + strings.Repeat(",?", len(query.DashboardIds)-1) + `)`) sb.WithTitle(query.Title)
for _, dashboardId := range query.DashboardIds {
params = append(params, dashboardId)
}
} }
if query.SignedInUser.OrgRole != m.ROLE_ADMIN { if len(query.Type) > 0 {
allowedDashboardsSubQuery := ` AND (dashboard.has_acl = 0 OR dashboard.id in ( sb.WithType(query.Type)
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 user_group_member as ugm on ugm.user_group_id = da.user_group_id
LEFT JOIN org_user ou on ou.role = da.role
WHERE
d.has_acl = 1 and
(da.user_id = ? or ugm.user_id = ? or ou.id is not null)
and d.org_id = ?
)
)`
sql.WriteString(allowedDashboardsSubQuery)
params = append(params, query.SignedInUser.UserId, query.SignedInUser.UserId, query.SignedInUser.OrgId)
} }
if len(query.Title) > 0 { if query.FolderId > 0 {
sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?") sb.WithFolderId(query.FolderId)
params = append(params, "%"+query.Title+"%")
} }
if len(query.Type) > 0 && query.Type == "dash-folder" { if len(query.ExpandedFolders) > 0 {
sql.WriteString(" AND dashboard.is_folder = 1") sb.WithExpandedFolders(query.ExpandedFolders)
} }
if len(query.Type) > 0 && query.Type == "dash-db" { var res []DashboardSearchProjection
sql.WriteString(" AND dashboard.is_folder = 0")
}
if query.FolderId > 0 { sql, params := sb.ToSql()
sql.WriteString(" AND dashboard.folder_id = ?") err := x.Sql(sql, params...).Find(&res)
params = append(params, query.FolderId) if err != nil {
return nil, err
} }
return params return res, nil
} }
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error { func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
......
...@@ -382,6 +382,19 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -382,6 +382,19 @@ func TestDashboardDataAccess(t *testing.T) {
currentUser := createUser("viewer", "Viewer", false) currentUser := createUser("viewer", "Viewer", false)
Convey("and one folder is expanded, the other collapsed", func() {
Convey("should return dashboards in root and expanded folder", func() {
query := &search.FindPersistedDashboardsQuery{ExpandedFolders: []int64{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() { Convey("and acl is set for one dashboard folder", func() {
var otherUser int64 = 999 var otherUser int64 = 999
updateTestDashboardWithAcl(folder1.Id, otherUser, m.PERMISSION_EDIT) updateTestDashboardWithAcl(folder1.Id, otherUser, m.PERMISSION_EDIT)
......
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
whereFolderId int64
expandedFolders []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) WithFolderId(folderId int64) *SearchBuilder {
sb.whereFolderId = folderId
return sb
}
func (sb *SearchBuilder) WithExpandedFolders(expandedFolders []int64) *SearchBuilder {
sb.expandedFolders = expandedFolders
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 = 0 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 user_group_member as ugm on ugm.user_group_id = da.user_group_id
LEFT JOIN org_user ou on ou.role = da.role
WHERE
d.has_acl = 1 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 = 1")
}
if sb.whereTypeDash {
sb.sql.WriteString(" AND dashboard.is_folder = 0")
}
if sb.whereFolderId > 0 {
sb.sql.WriteString(" AND dashboard.folder_id = ?")
sb.params = append(sb.params, sb.whereFolderId)
}
if len(sb.expandedFolders) > 0 {
sb.sql.WriteString(` AND (dashboard.folder_id IN (?` + strings.Repeat(",?", len(sb.expandedFolders)-1) + `) `)
sb.sql.WriteString(` OR dashboard.folder_id IS NULL OR dashboard.folder_id = 0)`)
for _, ef := range sb.expandedFolders {
sb.params = append(sb.params, ef)
}
}
}
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)
})
})
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment