Commit 448a8b8d by Torkel Ödegaard

Major refactorings around searching, moved to seperate package, trying to move…

Major refactorings around searching, moved to seperate package, trying to move stuff out of models package, extend search support searching different types of entities and different types of dashboards, #960
parent c8146e75
...@@ -216,7 +216,6 @@ exchange = grafana_events ...@@ -216,7 +216,6 @@ exchange = grafana_events
#################################### Dashboard JSON files ########################## #################################### Dashboard JSON files ##########################
[dashboards.json] [dashboards.json]
enabled = false enabled = false
path = dashboards path = /var/lib/grafana/dashboards
orgs = *
...@@ -211,3 +211,11 @@ ...@@ -211,3 +211,11 @@
;enabled = false ;enabled = false
;rabbitmq_url = amqp://localhost/ ;rabbitmq_url = amqp://localhost/
;exchange = grafana_events ;exchange = grafana_events
;#################################### Dashboard JSON files ##########################
[dashboards.json]
;enabled = false
;path = /var/lib/grafana/dashboards
...@@ -14,8 +14,8 @@ import ( ...@@ -14,8 +14,8 @@ import (
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/search"
"github.com/grafana/grafana/pkg/services/eventpublisher" "github.com/grafana/grafana/pkg/services/eventpublisher"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/social" "github.com/grafana/grafana/pkg/social"
......
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/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"
) )
......
...@@ -3,7 +3,7 @@ package api ...@@ -3,7 +3,7 @@ package api
import ( import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/search"
) )
func Search(c *middleware.Context) { func Search(c *middleware.Context) {
......
...@@ -126,3 +126,13 @@ type GetDashboardQuery struct { ...@@ -126,3 +126,13 @@ type GetDashboardQuery struct {
Result *Dashboard Result *Dashboard
} }
type DashboardTagCloudItem struct {
Term string `json:"term"`
Count int `json:"count"`
}
type GetDashboardTagsQuery struct {
OrgId int64
Result []*DashboardTagCloudItem
}
package models package models
type SearchResult struct { type SearchHit struct {
Dashboards []*DashboardSearchHit `json:"dashboards"`
Tags []*DashboardTagCloudItem `json:"tags"`
TagsOnly bool `json:"tagsOnly"`
}
type DashboardSearchHit struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Uri string `json:"uri"` Uri string `json:"uri"`
...@@ -14,24 +8,3 @@ type DashboardSearchHit struct { ...@@ -14,24 +8,3 @@ type DashboardSearchHit struct {
Tags []string `json:"tags"` Tags []string `json:"tags"`
IsStarred bool `json:"isStarred"` IsStarred bool `json:"isStarred"`
} }
type DashboardTagCloudItem struct {
Term string `json:"term"`
Count int `json:"count"`
}
type SearchDashboardsQuery struct {
Title string
Tag string
OrgId int64
UserId int64
Limit int
IsStarred bool
Result []*DashboardSearchHit
}
type GetDashboardTagsQuery struct {
OrgId int64
Result []*DashboardTagCloudItem
}
...@@ -2,23 +2,13 @@ package search ...@@ -2,23 +2,13 @@ package search
import ( import (
"path/filepath" "path/filepath"
"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" "github.com/grafana/grafana/pkg/setting"
) )
type Query struct {
Title string
Tag string
OrgId int64
UserId int64
Limit int
IsStarred bool
Result []*m.DashboardSearchHit
}
var jsonDashIndex *JsonDashIndex var jsonDashIndex *JsonDashIndex
func Init() { func Init() {
...@@ -33,15 +23,14 @@ func Init() { ...@@ -33,15 +23,14 @@ func Init() {
jsonFilesPath = filepath.Join(setting.HomePath, jsonFilesPath) jsonFilesPath = filepath.Join(setting.HomePath, jsonFilesPath)
} }
orgIds := jsonIndexCfg.Key("org_ids").String() jsonDashIndex = NewJsonDashIndex(jsonFilesPath)
jsonDashIndex = NewJsonDashIndex(jsonFilesPath, orgIds)
} }
} }
func searchHandler(query *Query) error { func searchHandler(query *Query) error {
hits := make([]*m.DashboardSearchHit, 0) hits := make(HitList, 0)
dashQuery := m.SearchDashboardsQuery{ dashQuery := FindPersistedDashboardsQuery{
Title: query.Title, Title: query.Title,
Tag: query.Tag, Tag: query.Tag,
UserId: query.UserId, UserId: query.UserId,
...@@ -65,6 +54,8 @@ func searchHandler(query *Query) error { ...@@ -65,6 +54,8 @@ func searchHandler(query *Query) error {
hits = append(hits, jsonHits...) hits = append(hits, jsonHits...)
} }
sort.Sort(hits)
if err := setIsStarredFlagOnSearchResults(query.UserId, hits); err != nil { if err := setIsStarredFlagOnSearchResults(query.UserId, hits); err != nil {
return err return err
} }
...@@ -73,7 +64,7 @@ func searchHandler(query *Query) error { ...@@ -73,7 +64,7 @@ func searchHandler(query *Query) error {
return nil return nil
} }
func setIsStarredFlagOnSearchResults(userId int64, hits []*m.DashboardSearchHit) 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 {
return err return err
......
...@@ -11,9 +11,8 @@ import ( ...@@ -11,9 +11,8 @@ import (
) )
type JsonDashIndex struct { type JsonDashIndex struct {
path string path string
orgsIds []int64 items []*JsonDashIndexItem
items []*JsonDashIndexItem
} }
type JsonDashIndexItem struct { type JsonDashIndexItem struct {
...@@ -23,7 +22,7 @@ type JsonDashIndexItem struct { ...@@ -23,7 +22,7 @@ type JsonDashIndexItem struct {
Dashboard *m.Dashboard Dashboard *m.Dashboard
} }
func NewJsonDashIndex(path string, orgIds string) *JsonDashIndex { func NewJsonDashIndex(path string) *JsonDashIndex {
log.Info("Creating json dashboard index for path: ", path) log.Info("Creating json dashboard index for path: ", path)
index := JsonDashIndex{} index := JsonDashIndex{}
...@@ -32,8 +31,8 @@ func NewJsonDashIndex(path string, orgIds string) *JsonDashIndex { ...@@ -32,8 +31,8 @@ func NewJsonDashIndex(path string, orgIds string) *JsonDashIndex {
return &index return &index
} }
func (index *JsonDashIndex) Search(query *Query) ([]*m.DashboardSearchHit, error) { func (index *JsonDashIndex) Search(query *Query) ([]*Hit, error) {
results := make([]*m.DashboardSearchHit, 0) results := make([]*Hit, 0)
for _, item := range index.items { for _, item := range index.items {
if len(results) > query.Limit { if len(results) > query.Limit {
...@@ -49,8 +48,8 @@ func (index *JsonDashIndex) Search(query *Query) ([]*m.DashboardSearchHit, error ...@@ -49,8 +48,8 @@ func (index *JsonDashIndex) Search(query *Query) ([]*m.DashboardSearchHit, error
// add results with matchig title filter // add results with matchig title filter
if strings.Contains(item.TitleLower, query.Title) { if strings.Contains(item.TitleLower, query.Title) {
results = append(results, &m.DashboardSearchHit{ results = append(results, &Hit{
Type: m.DashTypeJson, Type: DashHitJson,
Title: item.Dashboard.Title, Title: item.Dashboard.Title,
Tags: item.Dashboard.GetTags(), Tags: item.Dashboard.GetTags(),
Uri: "file/" + item.Path, Uri: "file/" + item.Path,
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
func TestJsonDashIndex(t *testing.T) { func TestJsonDashIndex(t *testing.T) {
Convey("Given the json dash index", t, func() { Convey("Given the json dash index", t, func() {
index := NewJsonDashIndex("../../../public/dashboards/", "*") index := NewJsonDashIndex("../../public/dashboards/", "*")
Convey("Should be able to update index", func() { Convey("Should be able to update index", func() {
err := index.updateIndex() err := index.updateIndex()
......
package search
type HitType string
const (
DashHitDB HitType = "dash-db"
DashHitHome HitType = "dash-home"
DashHitJson HitType = "dash-json"
DashHitScripted HitType = "dash-scripted"
)
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"`
}
type HitList []*Hit
func (s HitList) Len() int { return len(s) }
func (s HitList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s HitList) Less(i, j int) bool { return s[i].Title < s[j].Title }
type Query struct {
Title string
Tag string
OrgId int64
UserId int64
Limit int
IsStarred bool
Result HitList
}
type FindPersistedDashboardsQuery struct {
Title string
Tag string
OrgId int64
UserId int64
Limit int
IsStarred bool
Result HitList
}
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/search"
) )
func init() { func init() {
...@@ -119,7 +120,7 @@ type DashboardSearchProjection struct { ...@@ -119,7 +120,7 @@ type DashboardSearchProjection struct {
Term string Term string
} }
func SearchDashboards(query *m.SearchDashboardsQuery) error { func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
var sql bytes.Buffer var sql bytes.Buffer
params := make([]interface{}, 0) params := make([]interface{}, 0)
...@@ -166,17 +167,17 @@ func SearchDashboards(query *m.SearchDashboardsQuery) error { ...@@ -166,17 +167,17 @@ func SearchDashboards(query *m.SearchDashboardsQuery) error {
return err return err
} }
query.Result = make([]*m.DashboardSearchHit, 0) query.Result = make([]*search.Hit, 0)
hits := make(map[int64]*m.DashboardSearchHit) hits := make(map[int64]*search.Hit)
for _, item := range res { for _, item := range res {
hit, exists := hits[item.Id] hit, exists := hits[item.Id]
if !exists { if !exists {
hit = &m.DashboardSearchHit{ hit = &search.Hit{
Id: item.Id, Id: item.Id,
Title: item.Title, Title: item.Title,
Uri: "db/" + item.Slug, Uri: "db/" + item.Slug,
Type: m.DashTypeDB, Type: search.DashHitDB,
Tags: []string{}, Tags: []string{},
} }
query.Result = append(query.Result, hit) query.Result = append(query.Result, hit)
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/search"
) )
func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard { func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
...@@ -85,7 +86,7 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -85,7 +86,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 := search.FindPersistedDashboardsQuery{
Title: "test", Title: "test",
OrgId: 1, OrgId: 1,
} }
...@@ -99,8 +100,8 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -99,8 +100,8 @@ func TestDashboardDataAccess(t *testing.T) {
}) })
Convey("Should be able to search for dashboards using tags", func() { Convey("Should be able to search for dashboards using tags", func() {
query1 := m.SearchDashboardsQuery{Tag: "webapp", OrgId: 1} query1 := search.FindPersistedDashboardsQuery{Tag: "webapp", OrgId: 1}
query2 := m.SearchDashboardsQuery{Tag: "tagdoesnotexist", OrgId: 1} query2 := search.FindPersistedDashboardsQuery{Tag: "tagdoesnotexist", OrgId: 1}
err := SearchDashboards(&query1) err := SearchDashboards(&query1)
err = SearchDashboards(&query2) err = SearchDashboards(&query2)
...@@ -146,7 +147,7 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -146,7 +147,7 @@ func TestDashboardDataAccess(t *testing.T) {
}) })
Convey("Should be able to search for starred dashboards", func() { Convey("Should be able to search for starred dashboards", func() {
query := m.SearchDashboardsQuery{OrgId: 1, UserId: 10, IsStarred: true} query := search.FindPersistedDashboardsQuery{OrgId: 1, UserId: 10, IsStarred: true}
err := SearchDashboards(&query) err := SearchDashboards(&query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
......
...@@ -40,15 +40,15 @@ function (angular, _, config) { ...@@ -40,15 +40,15 @@ function (angular, _, config) {
$scope.moveSelection(-1); $scope.moveSelection(-1);
} }
if (evt.keyCode === 13) { if (evt.keyCode === 13) {
if ($scope.query.tagcloud) { if ($scope.tagMode) {
var tag = $scope.results.tags[$scope.selectedIndex]; var tag = $scope.results[$scope.selectedIndex];
if (tag) { if (tag) {
$scope.filterByTag(tag.term); $scope.filterByTag(tag.term);
} }
return; return;
} }
var selectedDash = $scope.results.dashboards[$scope.selectedIndex]; var selectedDash = $scope.results[$scope.selectedIndex];
if (selectedDash) { if (selectedDash) {
$location.search({}); $location.search({});
$location.path(selectedDash.url); $location.path(selectedDash.url);
...@@ -57,7 +57,9 @@ function (angular, _, config) { ...@@ -57,7 +57,9 @@ function (angular, _, config) {
}; };
$scope.moveSelection = function(direction) { $scope.moveSelection = function(direction) {
$scope.selectedIndex = Math.max(Math.min($scope.selectedIndex + direction, $scope.resultCount - 1), 0); var max = ($scope.results || []).length;
var newIndex = $scope.selectedIndex + direction;
$scope.selectedIndex = ((newIndex %= max) < 0) ? newIndex + max : newIndex;
}; };
$scope.searchDashboards = function() { $scope.searchDashboards = function() {
...@@ -68,14 +70,13 @@ function (angular, _, config) { ...@@ -68,14 +70,13 @@ function (angular, _, config) {
return backendSrv.search($scope.query).then(function(results) { return backendSrv.search($scope.query).then(function(results) {
if (localSearchId < $scope.currentSearchId) { return; } if (localSearchId < $scope.currentSearchId) { return; }
$scope.resultCount = results.length;
$scope.results = _.map(results, function(dash) { $scope.results = _.map(results, function(dash) {
dash.url = 'dashboard/' + dash.uri; dash.url = 'dashboard/' + dash.uri;
return dash; return dash;
}); });
if ($scope.queryHasNoFilters()) { if ($scope.queryHasNoFilters()) {
$scope.results.unshift({ title: 'Home', url: config.appSubUrl + '/', isHome: true }); $scope.results.unshift({ title: 'Home', url: config.appSubUrl + '/', type: 'dash-home' });
} }
}); });
}; };
...@@ -97,10 +98,10 @@ function (angular, _, config) { ...@@ -97,10 +98,10 @@ function (angular, _, config) {
}; };
$scope.getTags = function() { $scope.getTags = function() {
$scope.tagsMode = true;
return backendSrv.get('/api/dashboards/tags').then(function(results) { return backendSrv.get('/api/dashboards/tags').then(function(results) {
$scope.resultCount = results.length; $scope.tagsMode = true;
$scope.results = results; $scope.results = results;
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
}); });
}; };
...@@ -116,26 +117,6 @@ function (angular, _, config) { ...@@ -116,26 +117,6 @@ function (angular, _, config) {
$scope.searchDashboards(); $scope.searchDashboards();
}; };
$scope.addMetricToCurrentDashboard = function (metricId) {
$scope.dashboard.rows.push({
title: '',
height: '250px',
editable: true,
panels: [
{
type: 'graphite',
title: 'test',
span: 12,
targets: [{ target: metricId }]
}
]
});
};
$scope.toggleImport = function () {
$scope.showImport = !$scope.showImport;
};
$scope.newDashboard = function() { $scope.newDashboard = function() {
$location.url('dashboard/new'); $location.url('dashboard/new');
}; };
......
...@@ -133,12 +133,12 @@ function (angular, _) { ...@@ -133,12 +133,12 @@ function (angular, _) {
$scope.searchDashboards = function(link) { $scope.searchDashboards = function(link) {
return backendSrv.search({tag: link.tag}).then(function(results) { return backendSrv.search({tag: link.tag}).then(function(results) {
return _.reduce(results.dashboards, function(memo, dash) { return _.reduce(results, function(memo, dash) {
// do not add current dashboard // do not add current dashboard
if (dash.id !== currentDashId) { if (dash.id !== currentDashId) {
memo.push({ memo.push({
title: dash.title, title: dash.title,
url: 'dashboard/db/'+ dash.slug, url: 'dashboard/' + dash.uri,
icon: 'fa fa-th-large', icon: 'fa fa-th-large',
keepTime: link.keepTime, keepTime: link.keepTime,
includeVars: link.includeVars includeVars: link.includeVars
......
<grafana-panel> <grafana-panel>
<div class="dashlist"> <div class="dashlist">
<div class="dashlist-item" ng-repeat="dash in dashList"> <div class="dashlist-item" ng-repeat="dash in dashList">
<a class="dashlist-link" href="dashboard/{{dash.uri}}"> <a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}">
<span class="dashlist-title"> <span class="dashlist-title">
{{dash.title}} {{dash.title}}
</span> </span>
......
...@@ -62,7 +62,7 @@ function (angular, app, _, config, PanelMeta) { ...@@ -62,7 +62,7 @@ function (angular, app, _, config, PanelMeta) {
} }
return backendSrv.search(params).then(function(result) { return backendSrv.search(params).then(function(result) {
$scope.dashList = result.dashboards; $scope.dashList = result;
}); });
}; };
......
...@@ -24,41 +24,39 @@ ...@@ -24,41 +24,39 @@
</div> </div>
</div> </div>
<div ng-if="!showImport"> <div class="search-results-container" ng-if="tagsMode">
<div class="search-results-container" ng-if="tagsMode"> <div class="row">
<div class="row"> <div class="span6 offset1">
<div class="span6 offset1"> <div ng-repeat="tag in results" class="pointer" style="width: 180px; float: left;"
<div ng-repeat="tag in results" class="pointer" style="width: 180px; float: left;" ng-class="{'selected': $index === selectedIndex }"
ng-class="{'selected': $index === selectedIndex }" ng-click="filterByTag(tag.term, $event)">
ng-click="filterByTag(tag.term, $event)"> <a class="search-result-tag label label-tag" tag-color-from-name tag="tag.term">
<a class="search-result-tag label label-tag" tag-color-from-name tag="tag.term"> <i class="fa fa-tag"></i>
<i class="fa fa-tag"></i> <span>{{tag.term}} &nbsp;({{tag.count}})</span>
<span>{{tag.term}} &nbsp;({{tag.count}})</span> </a>
</a>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="search-results-container" ng-if="!tagsMode"> <div class="search-results-container" ng-if="!tagsMode">
<h6 ng-hide="results.length">No dashboards matching your query were found.</h6> <h6 ng-hide="results.length">No dashboards matching your query were found.</h6>
<a class="search-result-item pointer search-result-item-{{row.type}}" bindonce ng-repeat="row in results" <a class="search-item pointer search-item-{{row.type}}" bindonce ng-repeat="row in results"
ng-class="{'selected': $index == selectedIndex}" ng-href="{{row.url}}"> ng-class="{'selected': $index == selectedIndex}" ng-href="{{row.url}}">
<span class="search-result-tags"> <span class="search-result-tags">
<span ng-click="filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name tag="tag" class="label label-tag"> <span ng-click="filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name tag="tag" class="label label-tag">
{{tag}} {{tag}}
</span>
<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
</span> </span>
<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
</span>
<span class="search-result-link"> <span class="search-result-link">
<i class="search-result-icon"></i> <i class="fa search-result-icon"></i>
<span bo-text="row.title"></span> <span bo-text="row.title"></span>
</span> </span>
</a> </a>
</div>
</div> </div>
<div class="search-button-row"> <div class="search-button-row">
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
display: block; display: block;
line-height: 28px; line-height: 28px;
.search-result-item:hover, .search-result-item.selected { .search-item:hover, .search-item.selected {
background-color: @grafanaListHighlight; background-color: @grafanaListHighlight;
} }
...@@ -67,12 +67,19 @@ ...@@ -67,12 +67,19 @@
} }
} }
.search-result-item { .search-item {
display: block; display: block;
padding: 3px 10px; padding: 3px 10px;
white-space: nowrap; white-space: nowrap;
background-color: @grafanaListBackground; background-color: @grafanaListBackground;
margin-bottom: 4px; margin-bottom: 4px;
.search-result-icon:before {
content: "\f009";
}
&.search-item-dash-home .search-result-icon:before {
content: "\f015";
}
} }
.search-result-tags { .search-result-tags {
......
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