mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard search now supports filtering by multiple dashboard tags, Closes #2095
This commit is contained in:
parent
1a71da417c
commit
dc607b8e8a
@ -12,6 +12,7 @@
|
||||
- [Issue #2088](https://github.com/grafana/grafana/issues/2088). Roles: New user role `Read Only Editor` that replaces the old `Viewer` role behavior
|
||||
|
||||
**Backend**
|
||||
- [Issue #2095](https://github.com/grafana/grafana/issues/2095). Search: Search now supports filtering by multiple dashboard tags
|
||||
- [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski
|
||||
- [Issue #2052](https://github.com/grafana/grafana/issues/2052). Github OAuth: You can now configure a Github organization requirement, thx @indrekj
|
||||
- [Issue #1891](https://github.com/grafana/grafana/issues/1891). Security: New config option to disable the use of gravatar for profile images
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
|
||||
func Search(c *middleware.Context) {
|
||||
query := c.Query("query")
|
||||
tag := c.Query("tag")
|
||||
tags := c.QueryStrings("tag")
|
||||
starred := c.Query("starred")
|
||||
limit := c.QueryInt("limit")
|
||||
|
||||
@ -18,7 +18,7 @@ func Search(c *middleware.Context) {
|
||||
|
||||
searchQuery := search.Query{
|
||||
Title: query,
|
||||
Tag: tag,
|
||||
Tags: tags,
|
||||
UserId: c.UserId,
|
||||
Limit: limit,
|
||||
IsStarred: starred == "true",
|
||||
|
@ -33,7 +33,6 @@ func searchHandler(query *Query) error {
|
||||
|
||||
dashQuery := FindPersistedDashboardsQuery{
|
||||
Title: query.Title,
|
||||
Tag: query.Tag,
|
||||
UserId: query.UserId,
|
||||
Limit: query.Limit,
|
||||
IsStarred: query.IsStarred,
|
||||
@ -55,6 +54,22 @@ func searchHandler(query *Query) error {
|
||||
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 tags
|
||||
for _, hit := range hits {
|
||||
sort.Strings(hit.Tags)
|
||||
}
|
||||
|
||||
// add isStarred info
|
||||
if err := setIsStarredFlagOnSearchResults(query.UserId, hits); err != nil {
|
||||
return err
|
||||
@ -63,15 +78,29 @@ func searchHandler(query *Query) error {
|
||||
// sort main result array
|
||||
sort.Sort(hits)
|
||||
|
||||
// sort tags
|
||||
for _, hit := range hits {
|
||||
sort.Strings(hit.Tags)
|
||||
}
|
||||
|
||||
query.Result = hits
|
||||
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 {
|
||||
query := m.GetUserStarsQuery{UserId: userId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
|
@ -45,5 +45,17 @@ 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, 2)
|
||||
So(query.Result[0].Title, ShouldEqual, "BBAA")
|
||||
So(query.Result[1].Title, ShouldEqual, "CCAA")
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -56,13 +56,6 @@ func (index *JsonDashIndex) Search(query *Query) ([]*Hit, error) {
|
||||
break
|
||||
}
|
||||
|
||||
// filter out results with tag filter
|
||||
if query.Tag != "" {
|
||||
if !strings.Contains(item.TagsCsv, query.Tag) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// add results with matchig title filter
|
||||
if strings.Contains(item.TitleLower, query.Title) {
|
||||
results = append(results, &Hit{
|
||||
|
@ -17,14 +17,14 @@ func TestJsonDashIndex(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to search index", func() {
|
||||
res, err := index.Search(&Query{Title: "", Tag: "", Limit: 20})
|
||||
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", Tag: "", Limit: 20})
|
||||
res, err := index.Search(&Query{Title: "home", Limit: 20})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(res), ShouldEqual, 1)
|
||||
@ -32,7 +32,7 @@ func TestJsonDashIndex(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should not return when starred is filtered", func() {
|
||||
res, err := index.Search(&Query{Title: "", Tag: "", IsStarred: true})
|
||||
res, err := index.Search(&Query{Title: "", IsStarred: true})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(res), ShouldEqual, 0)
|
||||
|
@ -26,7 +26,7 @@ func (s HitList) Less(i, j int) bool { return s[i].Title < s[j].Title }
|
||||
|
||||
type Query struct {
|
||||
Title string
|
||||
Tag string
|
||||
Tags []string
|
||||
OrgId int64
|
||||
UserId int64
|
||||
Limit int
|
||||
@ -37,7 +37,6 @@ type Query struct {
|
||||
|
||||
type FindPersistedDashboardsQuery struct {
|
||||
Title string
|
||||
Tag string
|
||||
OrgId int64
|
||||
UserId int64
|
||||
Limit int
|
||||
|
@ -150,13 +150,8 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
params = append(params, "%"+query.Title+"%")
|
||||
}
|
||||
|
||||
if len(query.Tag) > 0 {
|
||||
sql.WriteString(" AND dashboard_tag.term=?")
|
||||
params = append(params, query.Tag)
|
||||
}
|
||||
|
||||
if query.Limit == 0 || query.Limit > 10000 {
|
||||
query.Limit = 300
|
||||
query.Limit = 1000
|
||||
}
|
||||
|
||||
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT %d", query.Limit))
|
||||
|
@ -99,18 +99,6 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(len(hit.Tags), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Should be able to search for dashboards using tags", func() {
|
||||
query1 := search.FindPersistedDashboardsQuery{Tag: "webapp", OrgId: 1}
|
||||
query2 := search.FindPersistedDashboardsQuery{Tag: "tagdoesnotexist", OrgId: 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() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
|
@ -14,7 +14,7 @@ function (angular, _, config) {
|
||||
$scope.giveSearchFocus = 0;
|
||||
$scope.selectedIndex = -1;
|
||||
$scope.results = [];
|
||||
$scope.query = { query: '', tag: '', starred: false };
|
||||
$scope.query = { query: '', tag: [], starred: false };
|
||||
$scope.currentSearchId = 0;
|
||||
|
||||
if ($scope.dashboardViewState.fullscreen) {
|
||||
@ -82,12 +82,11 @@ function (angular, _, config) {
|
||||
|
||||
$scope.queryHasNoFilters = function() {
|
||||
var query = $scope.query;
|
||||
return query.query === '' && query.starred === false && query.tag === '';
|
||||
return query.query === '' && query.starred === false && query.tag.length === 0;
|
||||
};
|
||||
|
||||
$scope.filterByTag = function(tag, evt) {
|
||||
$scope.query.tag = tag;
|
||||
$scope.query.tagcloud = false;
|
||||
$scope.query.tag.push(tag);
|
||||
$scope.search();
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
if (evt) {
|
||||
@ -96,6 +95,14 @@ function (angular, _, config) {
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeTag = function(tag, evt) {
|
||||
$scope.query.tag = _.without($scope.query.tag, tag);
|
||||
$scope.search();
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
};
|
||||
|
||||
$scope.getTags = function() {
|
||||
return backendSrv.get('/api/dashboards/tags').then(function(results) {
|
||||
$scope.tagsMode = true;
|
||||
|
@ -15,11 +15,14 @@
|
||||
<i class="fa fa-remove" ng-show="tagsMode"></i>
|
||||
tags
|
||||
</a>
|
||||
<span ng-show="query.tag">
|
||||
| <a ng-click="filterByTag('')" tag-color-from-name="query.tag" class="label label-tag" ng-if="query.tag">
|
||||
<i class="fa fa-remove"></i>
|
||||
{{query.tag}}
|
||||
</a>
|
||||
<span ng-if="query.tag.length">
|
||||
|
|
||||
<span ng-repeat="tagName in query.tag">
|
||||
<a ng-click="removeTag(tagName, $event)" tag-color-from-name="tagName" class="label label-tag">
|
||||
<i class="fa fa-remove"></i>
|
||||
{{tagName}}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user