Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
fc69d59c
Commit
fc69d59c
authored
Jun 23, 2017
by
Torkel Ödegaard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
dashboard folder search fix
parent
45622536
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
143 additions
and
251 deletions
+143
-251
pkg/api/dashboard.go
+4
-4
pkg/api/dashboard_test.go
+7
-7
pkg/models/dashboards.go
+3
-3
pkg/models/dashboards_test.go
+3
-3
pkg/services/search/handlers.go
+1
-1
pkg/services/search/handlers_test.go
+1
-3
pkg/services/search/models.go
+10
-9
pkg/services/sqlstore/dashboard.go
+17
-70
pkg/services/sqlstore/dashboard_acl.go
+3
-3
pkg/services/sqlstore/dashboard_test.go
+16
-37
pkg/services/sqlstore/migrations/dashboard_mig.go
+3
-3
public/app/core/components/search/search.html
+3
-18
public/app/core/components/search/search.ts
+38
-6
public/app/core/services/backend_srv.ts
+1
-1
public/app/features/dashboard/dashboard_ctrl.ts
+1
-1
public/app/features/dashboard/dashnav/dashnav.ts
+2
-2
public/app/features/dashboard/model.ts
+2
-2
public/app/features/dashboard/save_as_modal.ts
+1
-1
public/sass/components/_search.scss
+27
-77
No files found.
pkg/api/dashboard.go
View file @
fc69d59c
...
...
@@ -88,13 +88,13 @@ func GetDashboard(c *middleware.Context) Response {
Version
:
dash
.
Version
,
HasAcl
:
dash
.
HasAcl
,
IsFolder
:
dash
.
IsFolder
,
FolderId
:
dash
.
Parent
Id
,
FolderId
:
dash
.
Folder
Id
,
FolderTitle
:
"Root"
,
}
// lookup folder title
if
dash
.
Parent
Id
>
0
{
query
:=
m
.
GetDashboardQuery
{
Id
:
dash
.
Parent
Id
,
OrgId
:
c
.
OrgId
}
if
dash
.
Folder
Id
>
0
{
query
:=
m
.
GetDashboardQuery
{
Id
:
dash
.
Folder
Id
,
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
.
Parent
Id
>
0
{
if
dash
.
IsFolder
&&
dash
.
Folder
Id
>
0
{
return
ApiError
(
400
,
m
.
ErrDashboardFolderCannotHaveParent
.
Error
(),
nil
)
}
...
...
pkg/api/dashboard_test.go
View file @
fc69d59c
...
...
@@ -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
.
Parent
Id
=
1
fakeDash
.
Folder
Id
=
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
.
Parent
Id
,
"
folderId"
:
fakeDash
.
Folder
Id
,
"title"
:
fakeDash
.
Title
,
"id"
:
fakeDash
.
Id
,
}),
...
...
@@ -163,10 +163,10 @@ func TestDashboardApiEndpoint(t *testing.T) {
return
nil
})
invalidCmd
:=
m
.
SaveDashboardCommand
{
ParentId
:
fakeDash
.
Parent
Id
,
FolderId
:
fakeDash
.
Folder
Id
,
IsFolder
:
true
,
Dashboard
:
simplejson
.
NewFromAny
(
map
[
string
]
interface
{}{
"
parentId"
:
fakeDash
.
Parent
Id
,
"
folderId"
:
fakeDash
.
Folder
Id
,
"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
.
Parent
Id
=
1
fakeDash
.
Folder
Id
=
1
fakeDash
.
HasAcl
=
true
aclMockResp
:=
[]
*
m
.
DashboardAclInfoDTO
{
...
...
@@ -210,10 +210,10 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
cmd
:=
m
.
SaveDashboardCommand
{
ParentId
:
fakeDash
.
Parent
Id
,
FolderId
:
fakeDash
.
Folder
Id
,
Dashboard
:
simplejson
.
NewFromAny
(
map
[
string
]
interface
{}{
"id"
:
fakeDash
.
Id
,
"
parentId"
:
fakeDash
.
Parent
Id
,
"
folderId"
:
fakeDash
.
Folder
Id
,
"title"
:
fakeDash
.
Title
,
}),
}
...
...
pkg/models/dashboards.go
View file @
fc69d59c
...
...
@@ -48,7 +48,7 @@ type Dashboard struct {
UpdatedBy
int64
CreatedBy
int64
Parent
Id
int64
Folder
Id
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
.
Parent
Id
dash
.
FolderId
=
cmd
.
Folder
Id
dash
.
UpdateSlug
()
return
dash
}
...
...
@@ -144,7 +144,7 @@ type SaveDashboardCommand struct {
OrgId
int64
`json:"-"`
RestoredFrom
int
`json:"-"`
PluginId
string
`json:"-"`
ParentId
int64
`json:"parent
Id"`
FolderId
int64
`json:"folder
Id"`
IsFolder
bool
`json:"isFolder"`
Result
*
Dashboard
...
...
pkg/models/dashboards_test.go
View file @
fc69d59c
...
...
@@ -44,11 +44,11 @@ func TestDashboardModel(t *testing.T) {
json
:=
simplejson
.
New
()
json
.
Set
(
"title"
,
"test dash"
)
cmd
:=
&
SaveDashboardCommand
{
Dashboard
:
json
,
Parent
Id
:
1
}
cmd
:=
&
SaveDashboardCommand
{
Dashboard
:
json
,
Folder
Id
:
1
}
dash
:=
cmd
.
GetDashboardModel
()
Convey
(
"Should set
Parent
Id"
,
func
()
{
So
(
dash
.
Parent
Id
,
ShouldEqual
,
1
)
Convey
(
"Should set
Folder
Id"
,
func
()
{
So
(
dash
.
Folder
Id
,
ShouldEqual
,
1
)
})
})
}
pkg/services/search/handlers.go
View file @
fc69d59c
...
...
@@ -44,7 +44,7 @@ func searchHandler(query *Query) error {
IsStarred
:
query
.
IsStarred
,
DashboardIds
:
query
.
DashboardIds
,
Type
:
query
.
Type
,
Parent
Id
:
query
.
FolderId
,
Folder
Id
:
query
.
FolderId
,
Mode
:
query
.
Mode
,
}
...
...
pkg/services/search/handlers_test.go
View file @
fc69d59c
...
...
@@ -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
})
...
...
pkg/services/search/models.go
View file @
fc69d59c
...
...
@@ -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
Parent
Id
int64
Folder
Id
int64
Mode
string
Result
HitList
...
...
pkg/services/sqlstore/dashboard.go
View file @
fc69d59c
...
...
@@ -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
Parent
Id
int64
Folder
Id
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
.
Parent
Id
>
0
{
sql
.
WriteString
(
" AND dashboard.
parent
_id = ?"
)
params
=
append
(
params
,
query
.
Parent
Id
)
if
query
.
Folder
Id
>
0
{
sql
.
WriteString
(
" AND dashboard.
folder
_id = ?"
)
params
=
append
(
params
,
query
.
Folder
Id
)
}
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
{
...
...
pkg/services/sqlstore/dashboard_acl.go
View file @
fc69d59c
...
...
@@ -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
:=
`
...
...
pkg/services/sqlstore/dashboard_test.go
View file @
fc69d59c
...
...
@@ -11,10 +11,10 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func
insertTestDashboard
(
title
string
,
orgId
int64
,
parent
Id
int64
,
isFolder
bool
,
tags
...
interface
{})
*
m
.
Dashboard
{
func
insertTestDashboard
(
title
string
,
orgId
int64
,
folder
Id
int64
,
isFolder
bool
,
tags
...
interface
{})
*
m
.
Dashboard
{
cmd
:=
m
.
SaveDashboardCommand
{
OrgId
:
orgId
,
ParentId
:
parent
Id
,
FolderId
:
folder
Id
,
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
.
Parent
Id
,
ShouldBeGreaterThan
,
0
)
So
(
savedDash
.
Folder
Id
,
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
.
Parent
Id
,
ShouldEqual
,
0
)
So
(
savedFolder
.
Folder
Id
,
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
,
Parent
Id
:
savedFolder
.
Id
,
Folder
Id
:
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
parent
Id"
,
func
()
{
Convey
(
"Should be able to update dashboard and remove
folder
Id"
,
func
()
{
cmd
:=
m
.
SaveDashboardCommand
{
OrgId
:
1
,
Dashboard
:
simplejson
.
NewFromAny
(
map
[
string
]
interface
{}{
"id"
:
1
,
"title"
:
"
parent
Id"
,
"title"
:
"
folder
Id"
,
"tags"
:
[]
interface
{}{},
}),
Overwrite
:
true
,
Parent
Id
:
2
,
Folder
Id
:
2
,
}
err
:=
SaveDashboard
(
&
cmd
)
So
(
err
,
ShouldBeNil
)
So
(
cmd
.
Result
.
Parent
Id
,
ShouldEqual
,
2
)
So
(
cmd
.
Result
.
Folder
Id
,
ShouldEqual
,
2
)
cmd
=
m
.
SaveDashboardCommand
{
OrgId
:
1
,
Dashboard
:
simplejson
.
NewFromAny
(
map
[
string
]
interface
{}{
"id"
:
1
,
"title"
:
"
parent
Id"
,
"title"
:
"
folder
Id"
,
"tags"
:
[]
interface
{}{},
}),
Parent
Id
:
0
,
Folder
Id
:
0
,
Overwrite
:
true
,
}
...
...
@@ -245,7 +224,7 @@ func TestDashboardDataAccess(t *testing.T) {
err
=
GetDashboard
(
&
query
)
So
(
err
,
ShouldBeNil
)
So
(
query
.
Result
.
Parent
Id
,
ShouldEqual
,
0
)
So
(
query
.
Result
.
Folder
Id
,
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
,
Parent
Id
:
savedFolder
.
Id
,
Folder
Id
:
savedFolder
.
Id
,
SignedInUser
:
&
m
.
SignedInUser
{},
}
...
...
pkg/services/sqlstore/migrations/dashboard_mig.go
View file @
fc69d59c
...
...
@@ -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
{
...
...
public/app/core/components/search/search.html
View file @
fc69d59c
...
...
@@ -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>
...
...
public/app/core/components/search/search.ts
View file @
fc69d59c
...
...
@@ -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
;
});
}
...
...
public/app/core/services/backend_srv.ts
View file @
fc69d59c
...
...
@@ -211,7 +211,7 @@ export class BackendSrv {
return
this
.
post
(
'/api/dashboards/db/'
,
{
dashboard
:
dash
,
parentId
:
dash
.
parent
Id
,
folderId
:
dash
.
folder
Id
,
overwrite
:
options
.
overwrite
===
true
,
message
:
options
.
message
||
''
,
});
...
...
public/app/features/dashboard/dashboard_ctrl.ts
View file @
fc69d59c
...
...
@@ -129,7 +129,7 @@ export class DashboardCtrl {
};
$scope
.
onFolderChange
=
function
(
folder
)
{
$scope
.
dashboard
.
parent
Id
=
folder
.
id
;
$scope
.
dashboard
.
folder
Id
=
folder
.
id
;
$scope
.
dashboard
.
meta
.
folderId
=
folder
.
id
;
$scope
.
dashboard
.
meta
.
folderTitle
=
folder
.
title
;
};
...
...
public/app/features/dashboard/dashnav/dashnav.ts
View file @
fc69d59c
...
...
@@ -140,8 +140,8 @@ export class DashNavCtrl {
var
newWindow
=
window
.
open
(
uri
);
}
onFolderChange
(
parent
Id
)
{
this
.
dashboard
.
parentId
=
parent
Id
;
onFolderChange
(
folder
Id
)
{
this
.
dashboard
.
folderId
=
folder
Id
;
}
}
...
...
public/app/features/dashboard/model.ts
View file @
fc69d59c
...
...
@@ -36,7 +36,7 @@ export class DashboardModel {
meta
:
any
;
events
:
any
;
editMode
:
boolean
;
parent
Id
:
number
;
folder
Id
:
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
.
parent
Id
||
null
;
this
.
folderId
=
data
.
folder
Id
||
null
;
this
.
rows
=
[];
if
(
data
.
rows
)
{
...
...
public/app/features/dashboard/save_as_modal.ts
View file @
fc69d59c
...
...
@@ -73,7 +73,7 @@ export class SaveDashboardAsModalCtrl {
}
onFolderChange
(
folder
)
{
this
.
clone
.
parent
Id
=
folder
.
id
;
this
.
clone
.
folder
Id
=
folder
.
id
;
}
}
...
...
public/sass/components/_search.scss
View file @
fc69d59c
...
...
@@ -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
();
&
:hover
,
&
.selected
{
background-color
:
$tight-form-func-bg
;
@include
left-brand-border-gradient
();
}
}
.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-result-tags
{
float
:
right
;
&
:hover
{
@include
left-brand-border-gradient
();
background-color
:
$tight-form-func-bg
;
}
.search-result-actions
{
float
:
right
;
padding-left
:
20px
;
&
.selected
{
background-color
:
$tight-form-func-bg
;
}
}
.
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
;
&
--dash-db
,
&
--dash-child
{
.
search-result-icon
:
:
before
{
content
:
"\f009"
;
}
}
.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
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment