mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard search by tag, and tag cloud now works, god dam I hate SQL
This commit is contained in:
parent
bcdbec61d7
commit
1ae52d2472
2
grafana
2
grafana
@ -1 +1 @@
|
|||||||
Subproject commit d03949a735fd6ee486e278feb3b87f252be5ce96
|
Subproject commit 37ba2511d5aef8034b3f275e581e0c4206823854
|
@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/torkelo/grafana-pro/pkg/bus"
|
"github.com/torkelo/grafana-pro/pkg/bus"
|
||||||
@ -49,8 +50,8 @@ func DeleteDashboard(c *middleware.Context) {
|
|||||||
func Search(c *middleware.Context) {
|
func Search(c *middleware.Context) {
|
||||||
queryText := c.Query("q")
|
queryText := c.Query("q")
|
||||||
result := m.SearchResult{
|
result := m.SearchResult{
|
||||||
Dashboards: []m.DashboardSearchHit{},
|
Dashboards: []*m.DashboardSearchHit{},
|
||||||
Tags: []m.DashboardTagCloudItem{},
|
Tags: []*m.DashboardTagCloudItem{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(queryText, "tags!:") {
|
if strings.HasPrefix(queryText, "tags!:") {
|
||||||
@ -63,8 +64,13 @@ func Search(c *middleware.Context) {
|
|||||||
result.Tags = query.Result
|
result.Tags = query.Result
|
||||||
result.TagsOnly = true
|
result.TagsOnly = true
|
||||||
} else {
|
} else {
|
||||||
queryText := strings.TrimPrefix(queryText, "title:")
|
searchQueryRegEx, _ := regexp.Compile(`(tags:(\w*)\sAND\s)?(?:title:)?(.*)?`)
|
||||||
query := m.SearchDashboardsQuery{Query: queryText, AccountId: c.GetAccountId()}
|
matches := searchQueryRegEx.FindStringSubmatch(queryText)
|
||||||
|
query := m.SearchDashboardsQuery{
|
||||||
|
Title: matches[3],
|
||||||
|
Tag: matches[2],
|
||||||
|
AccountId: c.GetAccountId(),
|
||||||
|
}
|
||||||
err := bus.Dispatch(&query)
|
err := bus.Dispatch(&query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JsonApiErr(500, "Search failed", err)
|
c.JsonApiErr(500, "Search failed", err)
|
||||||
|
@ -18,22 +18,20 @@ type Dashboard struct {
|
|||||||
Slug string `xorm:"index(IX_AccountIdSlug)"`
|
Slug string `xorm:"index(IX_AccountIdSlug)"`
|
||||||
AccountId int64 `xorm:"index(IX_AccountIdSlug)"`
|
AccountId int64 `xorm:"index(IX_AccountIdSlug)"`
|
||||||
|
|
||||||
Created time.Time `xorm:"CREATED"`
|
Created time.Time
|
||||||
Updated time.Time `xorm:"UPDATED"`
|
Updated time.Time
|
||||||
|
|
||||||
Title string
|
Title string
|
||||||
Tags []string
|
|
||||||
Data map[string]interface{}
|
Data map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchResult struct {
|
type SearchResult struct {
|
||||||
Dashboards []DashboardSearchHit `json:"dashboards"`
|
Dashboards []*DashboardSearchHit `json:"dashboards"`
|
||||||
Tags []DashboardTagCloudItem `json:"tags"`
|
Tags []*DashboardTagCloudItem `json:"tags"`
|
||||||
TagsOnly bool `json:"tagsOnly"`
|
TagsOnly bool `json:"tagsOnly"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DashboardSearchHit struct {
|
type DashboardSearchHit struct {
|
||||||
Id string `json:"id"`
|
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
@ -45,15 +43,16 @@ type DashboardTagCloudItem struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SearchDashboardsQuery struct {
|
type SearchDashboardsQuery struct {
|
||||||
Query string
|
Title string
|
||||||
|
Tag string
|
||||||
AccountId int64
|
AccountId int64
|
||||||
|
|
||||||
Result []DashboardSearchHit
|
Result []*DashboardSearchHit
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetDashboardTagsQuery struct {
|
type GetDashboardTagsQuery struct {
|
||||||
AccountId int64
|
AccountId int64
|
||||||
Result []DashboardTagCloudItem
|
Result []*DashboardTagCloudItem
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaveDashboardCommand struct {
|
type SaveDashboardCommand struct {
|
||||||
@ -75,15 +74,6 @@ type GetDashboardQuery struct {
|
|||||||
Result *Dashboard
|
Result *Dashboard
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertToStringArray(arr []interface{}) []string {
|
|
||||||
b := make([]string, len(arr))
|
|
||||||
for i := range arr {
|
|
||||||
b[i] = arr[i].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDashboard(title string) *Dashboard {
|
func NewDashboard(title string) *Dashboard {
|
||||||
dash := &Dashboard{}
|
dash := &Dashboard{}
|
||||||
dash.Data = make(map[string]interface{})
|
dash.Data = make(map[string]interface{})
|
||||||
@ -93,12 +83,25 @@ func NewDashboard(title string) *Dashboard {
|
|||||||
return dash
|
return dash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dash *Dashboard) GetTags() []string {
|
||||||
|
jsonTags := dash.Data["tags"]
|
||||||
|
if jsonTags == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := jsonTags.([]interface{})
|
||||||
|
b := make([]string, len(arr))
|
||||||
|
for i := range arr {
|
||||||
|
b[i] = arr[i].(string)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
|
func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
|
||||||
dash := &Dashboard{}
|
dash := &Dashboard{}
|
||||||
dash.Data = cmd.Dashboard
|
dash.Data = cmd.Dashboard
|
||||||
dash.Title = dash.Data["title"].(string)
|
dash.Title = dash.Data["title"].(string)
|
||||||
dash.AccountId = cmd.AccountId
|
dash.AccountId = cmd.AccountId
|
||||||
dash.Tags = convertToStringArray(dash.Data["tags"].([]interface{}))
|
|
||||||
dash.UpdateSlug()
|
dash.UpdateSlug()
|
||||||
|
|
||||||
if dash.Data["id"] != nil {
|
if dash.Data["id"] != nil {
|
||||||
|
@ -35,6 +35,22 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
|||||||
_, err = sess.Id(dash.Id).Update(dash)
|
_, err = sess.Id(dash.Id).Update(dash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delete existing tabs
|
||||||
|
_, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert new tags
|
||||||
|
tags := dash.GetTags()
|
||||||
|
if len(tags) > 0 {
|
||||||
|
tagRows := make([]DashboardTag, len(tags))
|
||||||
|
for _, tag := range tags {
|
||||||
|
tagRows = append(tagRows, DashboardTag{Term: tag, DashboardId: dash.Id})
|
||||||
|
}
|
||||||
|
sess.InsertMulti(&tagRows)
|
||||||
|
}
|
||||||
|
|
||||||
cmd.Result = dash
|
cmd.Result = dash
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@ -55,24 +71,59 @@ func GetDashboard(query *m.GetDashboardQuery) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DashboardSearchProjection struct {
|
||||||
|
Id int64
|
||||||
|
Title string
|
||||||
|
Slug string
|
||||||
|
Term string
|
||||||
|
}
|
||||||
|
|
||||||
func SearchDashboards(query *m.SearchDashboardsQuery) error {
|
func SearchDashboards(query *m.SearchDashboardsQuery) error {
|
||||||
titleQuery := "%" + query.Query + "%"
|
titleQuery := "%" + query.Title + "%"
|
||||||
|
|
||||||
sess := x.Limit(100, 0).Where("account_id=? AND title LIKE ?", query.AccountId, titleQuery)
|
sess := x.Table("dashboard")
|
||||||
sess.Table("Dashboard")
|
sess.Join("LEFT OUTER", "dashboard_tag", "dashboard.id=dashboard_tag.dashboard_id")
|
||||||
|
sess.Where("account_id=? AND title LIKE ?", query.AccountId, titleQuery)
|
||||||
|
sess.Cols("dashboard.id", "dashboard.title", "dashboard.slug", "dashboard_tag.term")
|
||||||
|
sess.Limit(100, 0)
|
||||||
|
|
||||||
query.Result = make([]m.DashboardSearchHit, 0)
|
if len(query.Tag) > 0 {
|
||||||
err := sess.Find(&query.Result)
|
sess.And("dashboard_tag.term=?", query.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []DashboardSearchProjection
|
||||||
|
err := sess.Find(&res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Result = make([]*m.DashboardSearchHit, 0)
|
||||||
|
hits := make(map[int64]*m.DashboardSearchHit)
|
||||||
|
|
||||||
|
for _, item := range res {
|
||||||
|
hit, exists := hits[item.Id]
|
||||||
|
if !exists {
|
||||||
|
hit = &m.DashboardSearchHit{
|
||||||
|
Title: item.Title,
|
||||||
|
Slug: item.Slug,
|
||||||
|
Tags: []string{},
|
||||||
|
}
|
||||||
|
query.Result = append(query.Result, hit)
|
||||||
|
hits[item.Id] = hit
|
||||||
|
}
|
||||||
|
if len(item.Term) > 0 {
|
||||||
|
hit.Tags = append(hit.Tags, item.Term)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||||
query.Result = []m.DashboardTagCloudItem{
|
sess := x.Sql("select count() as count, term from dashboard_tag group by term")
|
||||||
m.DashboardTagCloudItem{Term: "test", Count: 10},
|
|
||||||
m.DashboardTagCloudItem{Term: "prod", Count: 20},
|
err := sess.Find(&query.Result)
|
||||||
}
|
return err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
||||||
|
@ -51,7 +51,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
|
|
||||||
Convey("Should be able to search for dashboard", func() {
|
Convey("Should be able to search for dashboard", func() {
|
||||||
query := m.SearchDashboardsQuery{
|
query := m.SearchDashboardsQuery{
|
||||||
Query: "%test%",
|
Title: "%test%",
|
||||||
AccountId: 1,
|
AccountId: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +59,20 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(len(query.Result), ShouldEqual, 1)
|
So(len(query.Result), ShouldEqual, 1)
|
||||||
|
hit := query.Result[0]
|
||||||
|
So(len(hit.Tags), ShouldEqual, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to search for dashboards using tags", func() {
|
||||||
|
query1 := m.SearchDashboardsQuery{Tag: "webapp", AccountId: 1}
|
||||||
|
query2 := m.SearchDashboardsQuery{Tag: "tagdoesnotexist", AccountId: 1}
|
||||||
|
|
||||||
|
err := SearchDashboards(&query1)
|
||||||
|
err = SearchDashboards(&query2)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(len(query1.Result), ShouldEqual, 1)
|
||||||
|
So(len(query2.Result), ShouldEqual, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Should not be able to save dashboard with same name", func() {
|
Convey("Should not be able to save dashboard with same name", func() {
|
||||||
@ -67,7 +81,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
Dashboard: map[string]interface{}{
|
Dashboard: map[string]interface{}{
|
||||||
"id": nil,
|
"id": nil,
|
||||||
"title": "test dash 23",
|
"title": "test dash 23",
|
||||||
"tags": make([]interface{}, 0),
|
"tags": []interface{}{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,8 +89,14 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to get dashboard tags", func() {
|
||||||
|
query := m.GetDashboardTagsQuery{}
|
||||||
|
|
||||||
|
err := GetDashboardTags(&query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(len(query.Result), ShouldEqual, 3)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,17 @@ var (
|
|||||||
UseSQLite3 bool
|
UseSQLite3 bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DashboardTag struct {
|
||||||
|
Id int64
|
||||||
|
DashboardId int64
|
||||||
|
Term string
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
tables = make([]interface{}, 0)
|
tables = make([]interface{}, 0)
|
||||||
|
|
||||||
tables = append(tables, new(m.Account), new(m.Dashboard),
|
tables = append(tables, new(m.Account), new(m.Dashboard),
|
||||||
new(m.Collaborator), new(m.DataSource))
|
new(m.Collaborator), new(m.DataSource), new(DashboardTag))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
|
Loading…
Reference in New Issue
Block a user