Commit 1ae52d24 by Torkel Ödegaard

Dashboard search by tag, and tag cloud now works, god dam I hate SQL

parent bcdbec61
Subproject commit d03949a735fd6ee486e278feb3b87f252be5ce96 Subproject commit 37ba2511d5aef8034b3f275e581e0c4206823854
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) { ...@@ -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) { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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 ( ...@@ -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() {
......
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