Commit 30662802 by Torkel Ödegaard

added code from #8504, and #8021

parent 1833c655
...@@ -238,7 +238,6 @@ func (hs *HttpServer) registerRoutes() { ...@@ -238,7 +238,6 @@ func (hs *HttpServer) registerRoutes() {
r.Post("/db", bind(m.SaveDashboardCommand{}), wrap(PostDashboard)) r.Post("/db", bind(m.SaveDashboardCommand{}), wrap(PostDashboard))
r.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), wrap(CalculateDashboardDiff)) r.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), wrap(CalculateDashboardDiff))
r.Get("/file/:file", GetDashboardFromJsonFile)
r.Get("/home", wrap(GetHomeDashboard)) r.Get("/home", wrap(GetHomeDashboard))
r.Get("/tags", GetDashboardTags) r.Get("/tags", GetDashboardTags)
r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard)) r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
......
...@@ -17,7 +17,6 @@ import ( ...@@ -17,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
...@@ -290,22 +289,6 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) { ...@@ -290,22 +289,6 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
row.Set("panels", panels) row.Set("panels", panels)
} }
func GetDashboardFromJsonFile(c *middleware.Context) {
file := c.Params(":file")
dashboard := search.GetDashboardFromJsonIndex(file)
if dashboard == nil {
c.JsonApiErr(404, "Dashboard not found", nil)
return
}
dash := dtos.DashboardFullWithMeta{Dashboard: dashboard.Data}
dash.Meta.Type = m.DashTypeJson
dash.Meta.CanEdit = c.SignedInUser.HasRole(m.ROLE_READ_ONLY_EDITOR)
c.JSON(200, &dash)
}
// GetDashboardVersions returns all dashboard versions as JSON // GetDashboardVersions returns all dashboard versions as JSON
func GetDashboardVersions(c *middleware.Context) Response { func GetDashboardVersions(c *middleware.Context) Response {
dashId := c.ParamsInt64(":dashboardId") dashId := c.ParamsInt64(":dashboardId")
......
...@@ -16,16 +16,11 @@ func Search(c *middleware.Context) { ...@@ -16,16 +16,11 @@ func Search(c *middleware.Context) {
limit := c.QueryInt("limit") limit := c.QueryInt("limit")
dashboardType := c.Query("type") dashboardType := c.Query("type")
folderId := c.QueryInt64("folderId") folderId := c.QueryInt64("folderId")
mode := c.Query("mode")
if limit == 0 { if limit == 0 {
limit = 1000 limit = 1000
} }
if mode == "" {
mode = "list"
}
dbids := make([]int64, 0) dbids := make([]int64, 0)
for _, id := range c.QueryStrings("dashboardIds") { for _, id := range c.QueryStrings("dashboardIds") {
dashboardId, err := strconv.ParseInt(id, 10, 64) dashboardId, err := strconv.ParseInt(id, 10, 64)
...@@ -44,7 +39,6 @@ func Search(c *middleware.Context) { ...@@ -44,7 +39,6 @@ func Search(c *middleware.Context) {
DashboardIds: dbids, DashboardIds: dbids,
Type: dashboardType, Type: dashboardType,
FolderId: folderId, FolderId: folderId,
Mode: mode,
} }
err := bus.Dispatch(&searchQuery) err := bus.Dispatch(&searchQuery)
......
package search package search
import ( import (
"log"
"path/filepath"
"sort" "sort"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
) )
var jsonDashIndex *JsonDashIndex
func Init() { func Init() {
bus.AddHandler("search", searchHandler) bus.AddHandler("search", searchHandler)
jsonIndexCfg, _ := setting.Cfg.GetSection("dashboards.json")
if jsonIndexCfg == nil {
log.Fatal("Config section missing: dashboards.json")
return
}
jsonIndexEnabled := jsonIndexCfg.Key("enabled").MustBool(false)
if jsonIndexEnabled {
jsonFilesPath := jsonIndexCfg.Key("path").String()
if !filepath.IsAbs(jsonFilesPath) {
jsonFilesPath = filepath.Join(setting.HomePath, jsonFilesPath)
}
jsonDashIndex = NewJsonDashIndex(jsonFilesPath)
go jsonDashIndex.updateLoop()
}
} }
func searchHandler(query *Query) error { func searchHandler(query *Query) error {
hits := make(HitList, 0)
dashQuery := FindPersistedDashboardsQuery{ dashQuery := FindPersistedDashboardsQuery{
Title: query.Title, Title: query.Title,
SignedInUser: query.SignedInUser, SignedInUser: query.SignedInUser,
...@@ -45,35 +19,17 @@ func searchHandler(query *Query) error { ...@@ -45,35 +19,17 @@ func searchHandler(query *Query) error {
DashboardIds: query.DashboardIds, DashboardIds: query.DashboardIds,
Type: query.Type, Type: query.Type,
FolderId: query.FolderId, FolderId: query.FolderId,
Mode: query.Mode, Tags: query.Tags,
Limit: query.Limit,
} }
if err := bus.Dispatch(&dashQuery); err != nil { if err := bus.Dispatch(&dashQuery); err != nil {
return err return err
} }
hits := make(HitList, 0)
hits = append(hits, dashQuery.Result...) hits = append(hits, dashQuery.Result...)
if jsonDashIndex != nil {
jsonHits, err := jsonDashIndex.Search(query)
if err != nil {
return err
}
hits = append(hits, jsonHits...)
}
// filter out results with tag filter
if len(query.Tags) > 0 {
filtered := HitList{}
for _, hit := range hits {
if hasRequiredTags(query.Tags, hit.Tags) {
filtered = append(filtered, hit)
}
}
hits = filtered
}
// sort main result array // sort main result array
sort.Sort(hits) sort.Sort(hits)
...@@ -95,25 +51,6 @@ func searchHandler(query *Query) error { ...@@ -95,25 +51,6 @@ func searchHandler(query *Query) error {
return nil return nil
} }
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
func hasRequiredTags(queryTags, hitTags []string) bool {
for _, queryTag := range queryTags {
if !stringInSlice(queryTag, hitTags) {
return false
}
}
return true
}
func setIsStarredFlagOnSearchResults(userId int64, hits []*Hit) error { func setIsStarredFlagOnSearchResults(userId int64, hits []*Hit) error {
query := m.GetUserStarsQuery{UserId: userId} query := m.GetUserStarsQuery{UserId: userId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
...@@ -128,10 +65,3 @@ func setIsStarredFlagOnSearchResults(userId int64, hits []*Hit) error { ...@@ -128,10 +65,3 @@ func setIsStarredFlagOnSearchResults(userId int64, hits []*Hit) error {
return nil return nil
} }
func GetDashboardFromJsonIndex(filename string) *m.Dashboard {
if jsonDashIndex == nil {
return nil
}
return jsonDashIndex.GetDashboard(filename)
}
...@@ -11,9 +11,7 @@ import ( ...@@ -11,9 +11,7 @@ import (
func TestSearch(t *testing.T) { func TestSearch(t *testing.T) {
Convey("Given search query", t, func() { Convey("Given search query", t, func() {
jsonDashIndex = NewJsonDashIndex("../../../public/dashboards/")
query := Query{Limit: 2000, SignedInUser: &m.SignedInUser{IsGrafanaAdmin: true}} query := Query{Limit: 2000, SignedInUser: &m.SignedInUser{IsGrafanaAdmin: true}}
bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error { bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error {
query.Result = HitList{ query.Result = HitList{
&Hit{Id: 16, Title: "CCAA", Type: "dash-db", Tags: []string{"BB", "AA"}}, &Hit{Id: 16, Title: "CCAA", Type: "dash-db", Tags: []string{"BB", "AA"}},
...@@ -54,17 +52,5 @@ func TestSearch(t *testing.T) { ...@@ -54,17 +52,5 @@ func TestSearch(t *testing.T) {
}) })
}) })
Convey("That filters by tag", func() {
query.Tags = []string{"BB", "AA"}
err := searchHandler(&query)
So(err, ShouldBeNil)
Convey("should return correct results", func() {
So(len(query.Result), ShouldEqual, 3)
So(query.Result[0].Title, ShouldEqual, "BBAA")
So(query.Result[2].Title, ShouldEqual, "CCAA")
})
})
}) })
} }
package search
import (
"os"
"path/filepath"
"strings"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
)
type JsonDashIndex struct {
path string
items []*JsonDashIndexItem
}
type JsonDashIndexItem struct {
TitleLower string
TagsCsv string
Path string
Dashboard *m.Dashboard
}
func NewJsonDashIndex(path string) *JsonDashIndex {
log.Info("Creating json dashboard index for path: %v", path)
index := JsonDashIndex{}
index.path = path
index.updateIndex()
return &index
}
func (index *JsonDashIndex) updateLoop() {
ticker := time.NewTicker(time.Minute)
for {
select {
case <-ticker.C:
if err := index.updateIndex(); err != nil {
log.Error(3, "Failed to update dashboard json index %v", err)
}
}
}
}
func (index *JsonDashIndex) Search(query *Query) ([]*Hit, error) {
results := make([]*Hit, 0)
if query.IsStarred {
return results, nil
}
queryStr := strings.ToLower(query.Title)
for _, item := range index.items {
if len(results) > query.Limit {
break
}
// add results with matchig title filter
if strings.Contains(item.TitleLower, queryStr) {
results = append(results, &Hit{
Type: DashHitJson,
Title: item.Dashboard.Title,
Tags: item.Dashboard.GetTags(),
Uri: "file/" + item.Path,
})
}
}
return results, nil
}
func (index *JsonDashIndex) GetDashboard(path string) *m.Dashboard {
for _, item := range index.items {
if item.Path == path {
return item.Dashboard
}
}
return nil
}
func (index *JsonDashIndex) updateIndex() error {
var items = make([]*JsonDashIndexItem, 0)
visitor := func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
if strings.HasSuffix(f.Name(), ".json") {
dash, err := loadDashboardFromFile(path)
if err != nil {
return err
}
items = append(items, dash)
}
return nil
}
if err := filepath.Walk(index.path, visitor); err != nil {
return err
}
index.items = items
return nil
}
func loadDashboardFromFile(filename string) (*JsonDashIndexItem, error) {
reader, err := os.Open(filename)
if err != nil {
return nil, err
}
defer reader.Close()
data, err := simplejson.NewFromReader(reader)
if err != nil {
return nil, err
}
stat, _ := os.Stat(filename)
item := &JsonDashIndexItem{}
item.Dashboard = m.NewDashboardFromJson(data)
item.TitleLower = strings.ToLower(item.Dashboard.Title)
item.TagsCsv = strings.Join(item.Dashboard.GetTags(), ",")
item.Path = stat.Name()
return item, nil
}
package search
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestJsonDashIndex(t *testing.T) {
Convey("Given the json dash index", t, func() {
index := NewJsonDashIndex("../../../public/dashboards/")
Convey("Should be able to update index", func() {
err := index.updateIndex()
So(err, ShouldBeNil)
})
Convey("Should be able to search index", func() {
res, err := index.Search(&Query{Title: "", Limit: 20})
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 3)
})
Convey("Should be able to search index by title", func() {
res, err := index.Search(&Query{Title: "home", Limit: 20})
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 1)
So(res[0].Title, ShouldEqual, "Home")
})
Convey("Should not return when starred is filtered", func() {
res, err := index.Search(&Query{Title: "", IsStarred: true})
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 0)
})
})
}
...@@ -8,8 +8,6 @@ type HitType string ...@@ -8,8 +8,6 @@ type HitType string
const ( const (
DashHitDB HitType = "dash-db" DashHitDB HitType = "dash-db"
DashHitHome HitType = "dash-home" DashHitHome HitType = "dash-home"
DashHitJson HitType = "dash-json"
DashHitScripted HitType = "dash-scripted"
DashHitFolder HitType = "dash-folder" DashHitFolder HitType = "dash-folder"
) )
...@@ -51,7 +49,6 @@ type Query struct { ...@@ -51,7 +49,6 @@ type Query struct {
Type string Type string
DashboardIds []int64 DashboardIds []int64
FolderId int64 FolderId int64
Mode string
Result HitList Result HitList
} }
...@@ -64,7 +61,8 @@ type FindPersistedDashboardsQuery struct { ...@@ -64,7 +61,8 @@ type FindPersistedDashboardsQuery struct {
DashboardIds []int64 DashboardIds []int64
Type string Type string
FolderId int64 FolderId int64
Mode string Tags []string
Limit int
Result HitList Result HitList
} }
...@@ -3,6 +3,7 @@ package sqlstore ...@@ -3,6 +3,7 @@ package sqlstore
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
...@@ -159,22 +160,49 @@ type DashboardSearchProjection struct { ...@@ -159,22 +160,49 @@ type DashboardSearchProjection struct {
} }
func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
limit := query.Limit
if limit == 0 {
limit = 1000
}
var sql bytes.Buffer var sql bytes.Buffer
params := make([]interface{}, 0) params := make([]interface{}, 0)
sql.WriteString(`SELECT sql.WriteString(`
SELECT
dashboard.id, dashboard.id,
dashboard.title, dashboard.title,
dashboard.slug, dashboard.slug,
dashboard_tag.term, dashboard_tag.term,
dashboard.is_folder, dashboard.is_folder,
dashboard.folder_id, dashboard.folder_id,
f.slug as folder_slug, folder.slug as folder_slug,
f.title as folder_title folder.title as folder_title
FROM dashboard FROM (
LEFT OUTER JOIN dashboard f on f.id = dashboard.folder_id SELECT
LEFT OUTER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id`) dashboard.id FROM dashboard
LEFT OUTER JOIN dashboard_tag ON dashboard_tag.dashboard_id = dashboard.id
`)
// add tags filter
if len(query.Tags) > 0 {
sql.WriteString(` WHERE dashboard_tag.term IN (?` + strings.Repeat(",?", len(query.Tags)-1) + `)`)
for _, tag := range query.Tags {
params = append(params, tag)
}
}
// this ends the inner select (tag filtered part)
sql.WriteString(`
GROUP BY dashboard.id HAVING COUNT(dashboard.id) >= ?
ORDER BY dashboard.title ASC LIMIT ?) as ids`)
params = append(params, len(query.Tags))
params = append(params, limit)
sql.WriteString(`
INNER JOIN dashboard on ids.id = dashboard.id
LEFT OUTER JOIN dashboard folder on folder.id = dashboard.folder_id
LEFT OUTER JOIN dashboard_tag on dashboard.id = dashboard_tag.dashboard_id`)
if query.IsStarred { if query.IsStarred {
sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id") sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
} }
...@@ -188,16 +216,10 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear ...@@ -188,16 +216,10 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
} }
if len(query.DashboardIds) > 0 { if len(query.DashboardIds) > 0 {
sql.WriteString(" AND (") sql.WriteString(` AND dashboard.id IN (?` + strings.Repeat(",?", len(query.DashboardIds)-1) + `)`)
for i, dashboardId := range query.DashboardIds { for _, dashboardId := range query.DashboardIds {
if i != 0 {
sql.WriteString(" OR")
}
sql.WriteString(" dashboard.id = ?")
params = append(params, dashboardId) params = append(params, dashboardId)
} }
sql.WriteString(")")
} }
if query.SignedInUser.OrgRole != m.ROLE_ADMIN { if query.SignedInUser.OrgRole != m.ROLE_ADMIN {
......
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