mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
dashboard folder search fix
This commit is contained in:
parent
456225365f
commit
fc69d59cae
@ -88,13 +88,13 @@ func GetDashboard(c *middleware.Context) Response {
|
||||
Version: dash.Version,
|
||||
HasAcl: dash.HasAcl,
|
||||
IsFolder: dash.IsFolder,
|
||||
FolderId: dash.ParentId,
|
||||
FolderId: dash.FolderId,
|
||||
FolderTitle: "Root",
|
||||
}
|
||||
|
||||
// lookup folder title
|
||||
if dash.ParentId > 0 {
|
||||
query := m.GetDashboardQuery{Id: dash.ParentId, OrgId: c.OrgId}
|
||||
if dash.FolderId > 0 {
|
||||
query := m.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Dashboard folder could not be read", err)
|
||||
}
|
||||
@ -170,7 +170,7 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
|
||||
return dashboardGuardianResponse(err)
|
||||
}
|
||||
|
||||
if dash.IsFolder && dash.ParentId > 0 {
|
||||
if dash.IsFolder && dash.FolderId > 0 {
|
||||
return ApiError(400, m.ErrDashboardFolderCannotHaveParent.Error(), nil)
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
Convey("Given a dashboard with a parent folder which does not have an acl", t, func() {
|
||||
fakeDash := m.NewDashboard("Child dash")
|
||||
fakeDash.Id = 1
|
||||
fakeDash.ParentId = 1
|
||||
fakeDash.FolderId = 1
|
||||
fakeDash.HasAcl = false
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
|
||||
@ -50,7 +50,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"parentId": fakeDash.ParentId,
|
||||
"folderId": fakeDash.FolderId,
|
||||
"title": fakeDash.Title,
|
||||
"id": fakeDash.Id,
|
||||
}),
|
||||
@ -163,10 +163,10 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
invalidCmd := m.SaveDashboardCommand{
|
||||
ParentId: fakeDash.ParentId,
|
||||
FolderId: fakeDash.FolderId,
|
||||
IsFolder: true,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"parentId": fakeDash.ParentId,
|
||||
"folderId": fakeDash.FolderId,
|
||||
"title": fakeDash.Title,
|
||||
}),
|
||||
}
|
||||
@ -183,7 +183,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
Convey("Given a dashboard with a parent folder which has an acl", t, func() {
|
||||
fakeDash := m.NewDashboard("Child dash")
|
||||
fakeDash.Id = 1
|
||||
fakeDash.ParentId = 1
|
||||
fakeDash.FolderId = 1
|
||||
fakeDash.HasAcl = true
|
||||
|
||||
aclMockResp := []*m.DashboardAclInfoDTO{
|
||||
@ -210,10 +210,10 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
cmd := m.SaveDashboardCommand{
|
||||
ParentId: fakeDash.ParentId,
|
||||
FolderId: fakeDash.FolderId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": fakeDash.Id,
|
||||
"parentId": fakeDash.ParentId,
|
||||
"folderId": fakeDash.FolderId,
|
||||
"title": fakeDash.Title,
|
||||
}),
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ type Dashboard struct {
|
||||
|
||||
UpdatedBy int64
|
||||
CreatedBy int64
|
||||
ParentId int64
|
||||
FolderId int64
|
||||
IsFolder bool
|
||||
HasAcl bool
|
||||
|
||||
@ -116,7 +116,7 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
|
||||
dash.OrgId = cmd.OrgId
|
||||
dash.PluginId = cmd.PluginId
|
||||
dash.IsFolder = cmd.IsFolder
|
||||
dash.ParentId = cmd.ParentId
|
||||
dash.FolderId = cmd.FolderId
|
||||
dash.UpdateSlug()
|
||||
return dash
|
||||
}
|
||||
@ -144,7 +144,7 @@ type SaveDashboardCommand struct {
|
||||
OrgId int64 `json:"-"`
|
||||
RestoredFrom int `json:"-"`
|
||||
PluginId string `json:"-"`
|
||||
ParentId int64 `json:"parentId"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
|
||||
Result *Dashboard
|
||||
|
@ -44,11 +44,11 @@ func TestDashboardModel(t *testing.T) {
|
||||
json := simplejson.New()
|
||||
json.Set("title", "test dash")
|
||||
|
||||
cmd := &SaveDashboardCommand{Dashboard: json, ParentId: 1}
|
||||
cmd := &SaveDashboardCommand{Dashboard: json, FolderId: 1}
|
||||
dash := cmd.GetDashboardModel()
|
||||
|
||||
Convey("Should set ParentId", func() {
|
||||
So(dash.ParentId, ShouldEqual, 1)
|
||||
Convey("Should set FolderId", func() {
|
||||
So(dash.FolderId, ShouldEqual, 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ func searchHandler(query *Query) error {
|
||||
IsStarred: query.IsStarred,
|
||||
DashboardIds: query.DashboardIds,
|
||||
Type: query.Type,
|
||||
ParentId: query.FolderId,
|
||||
FolderId: query.FolderId,
|
||||
Mode: query.Mode,
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,7 @@ func TestSearch(t *testing.T) {
|
||||
&Hit{Id: 10, Title: "AABB", Type: "dash-db", Tags: []string{"CC", "AA"}},
|
||||
&Hit{Id: 15, Title: "BBAA", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}},
|
||||
&Hit{Id: 25, Title: "bbAAa", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}},
|
||||
&Hit{Id: 17, Title: "FOLDER", Type: "dash-folder", Dashboards: []Hit{
|
||||
{Id: 18, Title: "ZZAA", Tags: []string{"ZZ"}},
|
||||
}},
|
||||
&Hit{Id: 17, Title: "FOLDER", Type: "dash-folder"},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -14,14 +14,15 @@ const (
|
||||
)
|
||||
|
||||
type Hit struct {
|
||||
Id int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Uri string `json:"uri"`
|
||||
Type HitType `json:"type"`
|
||||
Tags []string `json:"tags"`
|
||||
IsStarred bool `json:"isStarred"`
|
||||
ParentId int64 `json:"parentId"`
|
||||
Dashboards []Hit `json:"dashboards"`
|
||||
Id int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Uri string `json:"uri"`
|
||||
Type HitType `json:"type"`
|
||||
Tags []string `json:"tags"`
|
||||
IsStarred bool `json:"isStarred"`
|
||||
FolderId int64 `json:"folderId,omitempty"`
|
||||
FolderTitle string `json:"folderTitle,omitempty"`
|
||||
FolderSlug string `json:"folderSlug,omitempty"`
|
||||
}
|
||||
|
||||
type HitList []*Hit
|
||||
@ -62,7 +63,7 @@ type FindPersistedDashboardsQuery struct {
|
||||
IsStarred bool
|
||||
DashboardIds []int64
|
||||
Type string
|
||||
ParentId int64
|
||||
FolderId int64
|
||||
Mode string
|
||||
|
||||
Result HitList
|
||||
|
@ -81,7 +81,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
} else {
|
||||
dash.Version += 1
|
||||
dash.Data.Set("version", dash.Version)
|
||||
affectedRows, err = sess.MustCols("parent_id").Id(dash.Id).Update(dash)
|
||||
affectedRows, err = sess.MustCols("folder_id").Id(dash.Id).Update(dash)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -153,7 +153,7 @@ type DashboardSearchProjection struct {
|
||||
Slug string
|
||||
Term string
|
||||
IsFolder bool
|
||||
ParentId int64
|
||||
FolderId int64
|
||||
FolderSlug string
|
||||
FolderTitle string
|
||||
}
|
||||
@ -168,11 +168,11 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
|
||||
dashboard.slug,
|
||||
dashboard_tag.term,
|
||||
dashboard.is_folder,
|
||||
dashboard.parent_id,
|
||||
dashboard.folder_id,
|
||||
f.slug as folder_slug,
|
||||
f.title as folder_title
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard f on f.id = dashboard.parent_id
|
||||
LEFT OUTER JOIN dashboard f on f.id = dashboard.folder_id
|
||||
LEFT OUTER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id`)
|
||||
|
||||
if query.IsStarred {
|
||||
@ -204,7 +204,7 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
|
||||
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.parent_id = da.dashboard_id or d.id = da.dashboard_id
|
||||
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
|
||||
@ -230,9 +230,9 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
|
||||
sql.WriteString(" AND dashboard.is_folder = 0")
|
||||
}
|
||||
|
||||
if query.ParentId > 0 {
|
||||
sql.WriteString(" AND dashboard.parent_id = ?")
|
||||
params = append(params, query.ParentId)
|
||||
if query.FolderId > 0 {
|
||||
sql.WriteString(" AND dashboard.folder_id = ?")
|
||||
params = append(params, query.FolderId)
|
||||
}
|
||||
|
||||
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 1000"))
|
||||
@ -253,38 +253,11 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if query.Mode == "tree" {
|
||||
res, err = appendDashboardFolders(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
makeQueryResult(query, res)
|
||||
|
||||
if query.Mode == "tree" {
|
||||
convertToDashboardFolders(query)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// appends parent folders for any hits to the search result
|
||||
func appendDashboardFolders(res []DashboardSearchProjection) ([]DashboardSearchProjection, error) {
|
||||
for _, item := range res {
|
||||
if item.ParentId > 0 {
|
||||
res = append(res, DashboardSearchProjection{
|
||||
Id: item.ParentId,
|
||||
IsFolder: true,
|
||||
Slug: item.FolderSlug,
|
||||
Title: item.FolderTitle,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getHitType(item DashboardSearchProjection) search.HitType {
|
||||
var hitType search.HitType
|
||||
if item.IsFolder {
|
||||
@ -304,12 +277,14 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard
|
||||
hit, exists := hits[item.Id]
|
||||
if !exists {
|
||||
hit = &search.Hit{
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: "db/" + item.Slug,
|
||||
Type: getHitType(item),
|
||||
ParentId: item.ParentId,
|
||||
Tags: []string{},
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: "db/" + 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
|
||||
@ -320,34 +295,6 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@ -379,7 +326,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 parent_id = ?",
|
||||
"DELETE FROM dashboard WHERE folder_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
|
@ -40,7 +40,7 @@ func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error {
|
||||
|
||||
// Update dashboard HasAcl flag
|
||||
dashboard := m.Dashboard{HasAcl: true}
|
||||
if _, err := sess.Cols("has_acl").Where("id=? OR parent_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -105,7 +105,7 @@ func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error {
|
||||
HasAcl: true,
|
||||
}
|
||||
|
||||
if _, err := sess.Cols("has_acl").Where("id=? OR parent_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
dashboardFilter := fmt.Sprintf(`IN (
|
||||
SELECT %d
|
||||
UNION
|
||||
SELECT parent_id from dashboard where id = %d
|
||||
SELECT folder_id from dashboard where id = %d
|
||||
)`, query.DashboardId, query.DashboardId)
|
||||
|
||||
rawSQL := `
|
||||
|
@ -11,10 +11,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func insertTestDashboard(title string, orgId int64, parentId int64, isFolder bool, tags ...interface{}) *m.Dashboard {
|
||||
func insertTestDashboard(title string, orgId int64, folderId int64, isFolder bool, tags ...interface{}) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
ParentId: parentId,
|
||||
FolderId: folderId,
|
||||
IsFolder: isFolder,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
@ -45,13 +45,13 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(savedDash.Slug, ShouldEqual, "test-dash-23")
|
||||
So(savedDash.Id, ShouldNotEqual, 0)
|
||||
So(savedDash.IsFolder, ShouldBeFalse)
|
||||
So(savedDash.ParentId, ShouldBeGreaterThan, 0)
|
||||
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.ParentId, ShouldEqual, 0)
|
||||
So(savedFolder.FolderId, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to get dashboard", func() {
|
||||
@ -112,26 +112,6 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to search for dashboard and return in folder hierarchy", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
Title: "test dash 23",
|
||||
OrgId: 1,
|
||||
Mode: "tree",
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
hit := query.Result[0].Dashboards[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",
|
||||
@ -150,7 +130,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
Convey("Should be able to search for a dashboard folder's children", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
ParentId: savedFolder.Id,
|
||||
FolderId: savedFolder.Id,
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
@ -166,19 +146,18 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
Convey("should be able to find two dashboards by id", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
DashboardIds: []int64{2, 3},
|
||||
Mode: "tree",
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result[0].Dashboards), ShouldEqual, 2)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
|
||||
hit := query.Result[0].Dashboards[0]
|
||||
hit := query.Result[0]
|
||||
So(len(hit.Tags), ShouldEqual, 2)
|
||||
|
||||
hit2 := query.Result[0].Dashboards[1]
|
||||
hit2 := query.Result[1]
|
||||
So(len(hit2.Tags), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
@ -208,30 +187,30 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to update dashboard and remove parentId", func() {
|
||||
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": "parentId",
|
||||
"title": "folderId",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
Overwrite: true,
|
||||
ParentId: 2,
|
||||
FolderId: 2,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.Result.ParentId, ShouldEqual, 2)
|
||||
So(cmd.Result.FolderId, ShouldEqual, 2)
|
||||
|
||||
cmd = m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "parentId",
|
||||
"title": "folderId",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
ParentId: 0,
|
||||
FolderId: 0,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
@ -245,7 +224,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
err = GetDashboard(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.ParentId, ShouldEqual, 0)
|
||||
So(query.Result.FolderId, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to delete a dashboard folder and its children", func() {
|
||||
@ -255,7 +234,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
ParentId: savedFolder.Id,
|
||||
FolderId: savedFolder.Id,
|
||||
SignedInUser: &m.SignedInUser{},
|
||||
}
|
||||
|
||||
|
@ -137,9 +137,9 @@ func addDashboardMigration(mg *Migrator) {
|
||||
{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,
|
||||
// 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: true,
|
||||
}))
|
||||
|
||||
mg.AddMigration("Add column isFolder in dashboard", NewAddColumnMigration(dashboardV2, &Column{
|
||||
|
@ -53,8 +53,8 @@
|
||||
<div class="search-results-container" ng-if="!ctrl.tagsMode">
|
||||
<h6 ng-hide="ctrl.results.length">No dashboards matching your query were found.</h6>
|
||||
|
||||
<div bindonce ng-repeat="row in ctrl.results">
|
||||
<a class="search-item pointer search-item-{{row.type}}" ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{row.url}}">
|
||||
<div ng-repeat="row in ctrl.results">
|
||||
<a class="search-item search-item--{{::row.type}}" ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{row.url}}">
|
||||
<span class="search-result-tags">
|
||||
<span ng-click="ctrl.filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name="tag" class="label label-tag">
|
||||
{{tag}}
|
||||
@ -64,23 +64,8 @@
|
||||
|
||||
<span class="search-result-link">
|
||||
<i class="fa search-result-icon"></i>
|
||||
<span bo-text="row.title"></span>
|
||||
{{::row.title}}
|
||||
</span>
|
||||
|
||||
<a class="search-item search-item-child pointer search-item-{{child.type}}" ng-repeat="child in row.dashboards"
|
||||
ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{'dashboard/' + child.uri}}">
|
||||
<span class="search-result-tags">
|
||||
<span ng-click="ctrl.filterByTag(tag, $event)" ng-repeat="tag in child.tags" tag-color-from-name="tag" class="label label-tag">
|
||||
{{tag}}
|
||||
</span>
|
||||
<i class="fa" ng-class="{'fa-star': child.isStarred, 'fa-star-o': !child.isStarred}"></i>
|
||||
</span>
|
||||
|
||||
<span class="search-result-link">
|
||||
<i class="fa search-result-icon"></i>
|
||||
<span bo-text="child.title"></span>
|
||||
</span>
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -106,17 +106,49 @@ export class SearchCtrl {
|
||||
this.currentSearchId = this.currentSearchId + 1;
|
||||
var localSearchId = this.currentSearchId;
|
||||
|
||||
return this.backendSrv.search(this.query).then((results) => {
|
||||
return this.backendSrv.search(this.query).then(results => {
|
||||
if (localSearchId < this.currentSearchId) { return; }
|
||||
|
||||
this.results = _.map(results, function(dash) {
|
||||
dash.url = 'dashboard/' + dash.uri;
|
||||
return dash;
|
||||
let byId = _.groupBy(results, 'id');
|
||||
let byFolderId = _.groupBy(results, 'folderId');
|
||||
let finalList = [];
|
||||
|
||||
// add missing parent folders
|
||||
_.each(results, (hit, index) => {
|
||||
if (hit.folderId && !byId[hit.folderId]) {
|
||||
const folder = {
|
||||
id: hit.folderId,
|
||||
uri: `db/${hit.folderSlug}`,
|
||||
title: hit.folderTitle,
|
||||
type: 'dash-folder'
|
||||
};
|
||||
byId[hit.folderId] = folder;
|
||||
results.splice(index, 0, folder);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.queryHasNoFilters()) {
|
||||
this.results.unshift({ title: 'Home', url: config.appSubUrl + '/', type: 'dash-home' });
|
||||
// group by folder
|
||||
for (let hit of results) {
|
||||
if (hit.folderId) {
|
||||
hit.type = "dash-child";
|
||||
} else {
|
||||
finalList.push(hit);
|
||||
}
|
||||
|
||||
hit.url = 'dashboard/' + hit.uri;
|
||||
|
||||
if (hit.type === 'dash-folder') {
|
||||
if (!byFolderId[hit.id]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let child of byFolderId[hit.id]) {
|
||||
finalList.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.results = finalList;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -211,7 +211,7 @@ export class BackendSrv {
|
||||
|
||||
return this.post('/api/dashboards/db/', {
|
||||
dashboard: dash,
|
||||
parentId: dash.parentId,
|
||||
folderId: dash.folderId,
|
||||
overwrite: options.overwrite === true,
|
||||
message: options.message || '',
|
||||
});
|
||||
|
@ -129,7 +129,7 @@ export class DashboardCtrl {
|
||||
};
|
||||
|
||||
$scope.onFolderChange = function(folder) {
|
||||
$scope.dashboard.parentId = folder.id;
|
||||
$scope.dashboard.folderId = folder.id;
|
||||
$scope.dashboard.meta.folderId = folder.id;
|
||||
$scope.dashboard.meta.folderTitle= folder.title;
|
||||
};
|
||||
|
@ -140,8 +140,8 @@ export class DashNavCtrl {
|
||||
var newWindow = window.open(uri);
|
||||
}
|
||||
|
||||
onFolderChange(parentId) {
|
||||
this.dashboard.parentId = parentId;
|
||||
onFolderChange(folderId) {
|
||||
this.dashboard.folderId = folderId;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ export class DashboardModel {
|
||||
meta: any;
|
||||
events: any;
|
||||
editMode: boolean;
|
||||
parentId: number;
|
||||
folderId: number;
|
||||
|
||||
constructor(data, meta?) {
|
||||
if (!data) {
|
||||
@ -65,7 +65,7 @@ export class DashboardModel {
|
||||
this.version = data.version || 0;
|
||||
this.links = data.links || [];
|
||||
this.gnetId = data.gnetId || null;
|
||||
this.parentId = data.parentId || null;
|
||||
this.folderId = data.folderId || null;
|
||||
|
||||
this.rows = [];
|
||||
if (data.rows) {
|
||||
|
@ -73,7 +73,7 @@ export class SaveDashboardAsModalCtrl {
|
||||
}
|
||||
|
||||
onFolderChange(folder) {
|
||||
this.clone.parentId = folder.id;
|
||||
this.clone.folderId = folder.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,95 +104,45 @@
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-item {
|
||||
word-wrap: break-word;
|
||||
display: block;
|
||||
padding: 3px 10px;
|
||||
white-space: nowrap;
|
||||
background-color: $tight-form-bg;
|
||||
margin-bottom: 4px;
|
||||
@include left-brand-border();
|
||||
.search-item {
|
||||
word-wrap: break-word;
|
||||
display: block;
|
||||
padding: 3px 10px;
|
||||
white-space: nowrap;
|
||||
background-color: $tight-form-bg;
|
||||
margin-bottom: 4px;
|
||||
@include left-brand-border();
|
||||
|
||||
&:hover,
|
||||
&.selected {
|
||||
background-color: $tight-form-func-bg;
|
||||
@include left-brand-border-gradient();
|
||||
&:hover {
|
||||
@include left-brand-border-gradient();
|
||||
background-color: $tight-form-func-bg;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: $tight-form-func-bg;
|
||||
}
|
||||
|
||||
&--dash-db,
|
||||
&--dash-child {
|
||||
.search-result-icon::before {
|
||||
content: "\f009";
|
||||
}
|
||||
}
|
||||
|
||||
.search-result-tags {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.search-result-actions {
|
||||
float: right;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-result-icon::before {
|
||||
content: "\f009";
|
||||
}
|
||||
|
||||
.search-item-dash-home .search-result-icon::before {
|
||||
content: "\f015";
|
||||
}
|
||||
|
||||
.search-item-dash-home .search-result-icon::before {
|
||||
content: "\f015";
|
||||
}
|
||||
|
||||
.search-item-child {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.search-item-dash-home > .search-result-link > .search-result-icon::before {
|
||||
content: "\f015";
|
||||
}
|
||||
|
||||
.search-item-dash-folder > .search-result-link > .search-result-icon::before {
|
||||
content: "\f07c";
|
||||
}
|
||||
|
||||
.search-button-row {
|
||||
padding: $spacer*2;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-around;
|
||||
height: 30%;
|
||||
|
||||
button, a {
|
||||
margin-bottom: $spacer;
|
||||
}
|
||||
|
||||
.search-button-row-explore-link {
|
||||
color: $gray-3;
|
||||
font-size: $font-size-sm;
|
||||
position: relative;
|
||||
top: 1.0rem;
|
||||
&:hover {
|
||||
color: $link-hover-color;
|
||||
}
|
||||
img {
|
||||
vertical-align: text-bottom;
|
||||
&--dash-folder {
|
||||
.search-result-icon::before {
|
||||
content: "\f07c";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
.search-dropdown {
|
||||
flex-direction: row;
|
||||
}
|
||||
.search-button-row {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
&--dash-child {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.search-container {
|
||||
left: 78px;
|
||||
}
|
||||
.search-result-tags {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user