WIP: rough prototype of dashboard folders

Breaks some stuff like selected dash in the search result.
In dashboard search, if the user is not searching then the result is
returned as a tree structure. No ACL's or user group ux yet.
This commit is contained in:
Daniel Lee
2017-06-12 15:49:09 +02:00
parent d10d897d65
commit 1248728d7f
15 changed files with 306 additions and 45 deletions
+1 -1
View File
@@ -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{
{
+68 -12
View File
@@ -148,13 +148,15 @@ 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
ParentId int64
}
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
var sql bytes.Buffer
params := make([]interface{}, 0)
@@ -162,7 +164,9 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
dashboard.id,
dashboard.title,
dashboard.slug,
dashboard_tag.term
dashboard_tag.term,
dashboard.is_folder,
dashboard.parent_id
FROM dashboard
LEFT OUTER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id`)
@@ -200,8 +204,16 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 1000"))
var res []DashboardSearchProjection
err := x.Sql(sql.String(), 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
}
@@ -213,11 +225,12 @@ 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,
Type: getHitType(item),
ParentId: item.ParentId,
Tags: []string{},
}
query.Result = append(query.Result, hit)
hits[item.Id] = hit
@@ -227,9 +240,52 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
}
}
if query.BrowseMode {
convertToDashboardFolders(query)
}
return err
}
func getHitType(item DashboardSearchProjection) search.HitType {
var hitType search.HitType
if item.IsFolder {
hitType = search.DashHitFolder
} else {
hitType = search.DashHitDB
}
return hitType
}
func convertToDashboardFolders(query *search.FindPersistedDashboardsQuery) error {
root := make(map[int64]*search.Hit)
var keys []int64
// Add dashboards and folders that should be at the root level
for _, item := range query.Result {
if item.Type == search.DashHitFolder || item.ParentId == 0 {
root[item.Id] = item
keys = append(keys, item.Id)
}
}
// Populate folders with their child dashboards
for _, item := range query.Result {
if item.Type == search.DashHitDB && item.ParentId > 0 {
root[item.ParentId].Dashboards = append(root[item.ParentId].Dashboards, *item)
}
}
query.Result = make([]*search.Hit, 0)
for _, key := range keys {
query.Result = append(query.Result, root[key])
}
return nil
}
func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
sql := `SELECT
COUNT(*) as count,
+53 -8
View File
@@ -11,9 +11,11 @@ import (
"github.com/grafana/grafana/pkg/services/search"
)
func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
func insertTestDashboard(title string, orgId int64, parentId int64, isFolder bool, tags ...interface{}) *m.Dashboard {
cmd := m.SaveDashboardCommand{
OrgId: orgId,
OrgId: orgId,
ParentId: parentId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
@@ -33,14 +35,23 @@ func TestDashboardDataAccess(t *testing.T) {
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.ParentId, 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.ParentId, ShouldEqual, 0)
})
Convey("Should be able to get dashboard", func() {
@@ -54,10 +65,11 @@ 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")
insertTestDashboard("delete me", 1, 0, false, "delete this")
dashboardSlug := slug.Make("delete me")
@@ -114,12 +126,45 @@ 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.DashHitDB)
So(hit.ParentId, ShouldBeGreaterThan, 0)
})
Convey("Should be able to search for dashboard folder", func() {
query := search.FindPersistedDashboardsQuery{
Title: "1 test dash folder",
OrgId: 1,
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
hit := query.Result[0]
So(hit.Type, ShouldEqual, search.DashHitFolder)
})
Convey("Should be able to browse dashboard folders", func() {
query := search.FindPersistedDashboardsQuery{
OrgId: 1,
BrowseMode: true,
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
hit := query.Result[0]
So(hit.Type, ShouldEqual, search.DashHitFolder)
So(len(hit.Dashboards), ShouldEqual, 2)
So(hit.Dashboards[0].Title, ShouldEqual, "test dash 23")
})
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},
DashboardIds: []int{2, 3},
OrgId: 1,
}
@@ -171,7 +216,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,
@@ -136,4 +136,40 @@ 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 parent_id for dashboard folder structure
mg.AddMigration("Add column parent_id in dashboard", NewAddColumnMigration(dashboardV2, &Column{
Name: "parent_id", Type: DB_BigInt, Nullable: true,
}))
mg.AddMigration("Add column isFolder in dashboard", NewAddColumnMigration(dashboardV2, &Column{
Name: "is_folder", Type: DB_Bool, Nullable: false, Default: "0",
}))
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: "user_group_id", Type: DB_BigInt, Nullable: true},
{Name: "permissions", Type: DB_SmallInt, Default: "4"},
{Name: "created", Type: DB_DateTime, Nullable: false},
{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
{Cols: []string{"org_id"}},
{Cols: []string{"dashboard_id", "user_id"}, Type: UniqueIndex},
{Cols: []string{"dashboard_id", "user_group_id"}, Type: UniqueIndex},
},
}
mg.AddMigration("create dashboard acl table", NewAddTableMigration(dashboardAclV1))
//------- indexes ------------------
mg.AddMigration("add unique index dashboard_acl_org_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_group_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[2]))
}
@@ -26,6 +26,7 @@ func AddMigrations(mg *Migrator) {
addAnnotationMig(mg)
addTestDataMigrations(mg)
addDashboardVersionMigration(mg)
addUserGroupMigrations(mg)
}
func addMigrationLogMigrations(mg *Migrator) {
@@ -0,0 +1,48 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addUserGroupMigrations(mg *Migrator) {
userGroupV1 := Table{
Name: "user_group",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "name", Type: DB_NVarchar, Length: 255, 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 user group table", NewAddTableMigration(userGroupV1))
//------- indexes ------------------
mg.AddMigration("add index user_group.org_id", NewAddIndexMigration(userGroupV1, userGroupV1.Indices[0]))
mg.AddMigration("add unique index user_group_org_id_name", NewAddIndexMigration(userGroupV1, userGroupV1.Indices[1]))
userGroupMemberV1 := Table{
Name: "user_group_member",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: DB_BigInt},
{Name: "user_group_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", "user_group_id", "user_id"}, Type: UniqueIndex},
},
}
mg.AddMigration("create user group member table", NewAddTableMigration(userGroupMemberV1))
//------- indexes ------------------
mg.AddMigration("add index user_group_member.org_id", NewAddIndexMigration(userGroupMemberV1, userGroupMemberV1.Indices[0]))
mg.AddMigration("add unique index user_group_member_org_id_user_group_id_user_id", NewAddIndexMigration(userGroupMemberV1, userGroupMemberV1.Indices[1]))
}