diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 32a6d031940..0b23cc40171 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -91,8 +91,8 @@ func GetAlerts(c *models.ReqContext) response.Response { } for _, d := range searchQuery.Result { - if d.Type == search.DashHitDB && d.Id > 0 { - dashboardIDs = append(dashboardIDs, d.Id) + if d.Type == search.DashHitDB && d.ID > 0 { + dashboardIDs = append(dashboardIDs, d.ID) } } diff --git a/pkg/api/alerting_test.go b/pkg/api/alerting_test.go index 3cedc534c50..739eb1b3beb 100644 --- a/pkg/api/alerting_test.go +++ b/pkg/api/alerting_test.go @@ -112,8 +112,8 @@ func TestAlertingAPIEndpoint(t *testing.T) { bus.AddHandler("test", func(query *search.Query) error { searchQuery = query query.Result = search.HitList{ - &search.Hit{Id: 1}, - &search.Hit{Id: 2}, + &search.Hit{ID: 1}, + &search.Hit{ID: 2}, } return nil }) diff --git a/pkg/api/playlist_play.go b/pkg/api/playlist_play.go index 647a6734cf9..b550f855847 100644 --- a/pkg/api/playlist_play.go +++ b/pkg/api/playlist_play.go @@ -51,11 +51,11 @@ func populateDashboardsByTag(orgID int64, signedInUser *models.SignedInUser, das if err := bus.Dispatch(&searchQuery); err == nil { for _, item := range searchQuery.Result { result = append(result, dtos.PlaylistDashboard{ - Id: item.Id, + Id: item.ID, Slug: item.Slug, Title: item.Title, - Uri: item.Uri, - Url: item.Url, + Uri: item.URI, + Url: item.URL, Order: dashboardTagOrder[tag], }) } diff --git a/pkg/api/search.go b/pkg/api/search.go index 4f095086766..c98cdfed4d9 100644 --- a/pkg/api/search.go +++ b/pkg/api/search.go @@ -80,6 +80,7 @@ func (hs *HTTPServer) ListSortOptions(c *models.ReqContext) response.Response { "name": o.Name, "displayName": o.DisplayName, "description": o.Description, + "meta": o.MetaName, }) } diff --git a/pkg/services/dashboards/folder_service.go b/pkg/services/dashboards/folder_service.go index c2692dd7c1c..df7fb229601 100644 --- a/pkg/services/dashboards/folder_service.go +++ b/pkg/services/dashboards/folder_service.go @@ -46,8 +46,8 @@ func (dr *dashboardServiceImpl) GetFolders(limit int64) ([]*models.Folder, error for _, hit := range searchQuery.Result { folders = append(folders, &models.Folder{ - Id: hit.Id, - Uid: hit.Uid, + Id: hit.ID, + Uid: hit.UID, Title: hit.Title, }) } diff --git a/pkg/services/search/hits.go b/pkg/services/search/hits.go index 8da535d7005..9604abd0f09 100644 --- a/pkg/services/search/hits.go +++ b/pkg/services/search/hits.go @@ -11,19 +11,20 @@ const ( ) type Hit struct { - Id int64 `json:"id"` - Uid string `json:"uid"` + ID int64 `json:"id"` + UID string `json:"uid"` Title string `json:"title"` - Uri string `json:"uri"` - Url string `json:"url"` + URI string `json:"uri"` + URL string `json:"url"` Slug string `json:"slug"` Type HitType `json:"type"` Tags []string `json:"tags"` IsStarred bool `json:"isStarred"` - FolderId int64 `json:"folderId,omitempty"` - FolderUid string `json:"folderUid,omitempty"` + FolderID int64 `json:"folderId,omitempty"` + FolderUID string `json:"folderUid,omitempty"` FolderTitle string `json:"folderTitle,omitempty"` - FolderUrl string `json:"folderUrl,omitempty"` + FolderURL string `json:"folderUrl,omitempty"` + SortMeta string `json:"sortMeta,omitempty"` } type HitList []*Hit diff --git a/pkg/services/search/service.go b/pkg/services/search/service.go index 7bfae6cf02f..ab34b89116b 100644 --- a/pkg/services/search/service.go +++ b/pkg/services/search/service.go @@ -43,6 +43,7 @@ type FindPersistedDashboardsQuery struct { Limit int64 Page int64 Permission models.PermissionType + Sort SortOption Filters []interface{} @@ -81,9 +82,7 @@ func (s *SearchService) searchHandler(query *Query) error { } if sortOpt, exists := s.sortOptions[query.Sort]; exists { - for _, filter := range sortOpt.Filter { - dashboardQuery.Filters = append(dashboardQuery.Filters, filter) - } + dashboardQuery.Sort = sortOpt } if err := bus.Dispatch(&dashboardQuery); err != nil { @@ -127,7 +126,7 @@ func setStarredDashboards(userID int64, hits []*Hit) error { } for _, dashboard := range hits { - if _, ok := query.Result[dashboard.Id]; ok { + if _, ok := query.Result[dashboard.ID]; ok { dashboard.IsStarred = true } } diff --git a/pkg/services/search/service_test.go b/pkg/services/search/service_test.go index 120b4530b83..aa3dc844982 100644 --- a/pkg/services/search/service_test.go +++ b/pkg/services/search/service_test.go @@ -12,11 +12,11 @@ import ( func TestSearch_SortedResults(t *testing.T) { bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error { query.Result = HitList{ - &Hit{Id: 16, Title: "CCAA", Type: "dash-db", Tags: []string{"BB", "AA"}}, - &Hit{Id: 10, Title: "AABB", Type: "dash-db", Tags: []string{"CC", "AA"}}, - &Hit{Id: 15, Title: "BBAA", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}}, - &Hit{Id: 25, Title: "bbAAa", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}}, - &Hit{Id: 17, Title: "FOLDER", Type: "dash-folder"}, + &Hit{ID: 16, Title: "CCAA", Type: "dash-db", Tags: []string{"BB", "AA"}}, + &Hit{ID: 10, Title: "AABB", Type: "dash-db", Tags: []string{"CC", "AA"}}, + &Hit{ID: 15, Title: "BBAA", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}}, + &Hit{ID: 25, Title: "bbAAa", Type: "dash-db", Tags: []string{"EE", "AA", "BB"}}, + &Hit{ID: 17, Title: "FOLDER", Type: "dash-folder"}, } return nil }) diff --git a/pkg/services/search/sorting.go b/pkg/services/search/sorting.go index e67d98af02d..dfa2e6fe2f9 100644 --- a/pkg/services/search/sorting.go +++ b/pkg/services/search/sorting.go @@ -9,16 +9,18 @@ import ( var ( sortAlphaAsc = SortOption{ Name: "alpha-asc", - DisplayName: "Alphabetically (A-Z)", + DisplayName: "Alphabetically (A–Z)", Description: "Sort results in an alphabetically ascending order", + Index: 0, Filter: []SortOptionFilter{ searchstore.TitleSorter{}, }, } sortAlphaDesc = SortOption{ Name: "alpha-desc", - DisplayName: "Alphabetically (Z-A)", + DisplayName: "Alphabetically (Z–A)", Description: "Sort results in an alphabetically descending order", + Index: 0, Filter: []SortOptionFilter{ searchstore.TitleSorter{Descending: true}, }, @@ -29,6 +31,8 @@ type SortOption struct { Name string DisplayName string Description string + Index int + MetaName string Filter []SortOptionFilter } @@ -48,7 +52,7 @@ func (s *SearchService) SortOptions() []SortOption { opts = append(opts, o) } sort.Slice(opts, func(i, j int) bool { - return opts[i].Name < opts[j].Name + return opts[i].Index < opts[j].Index || (opts[i].Index == opts[j].Index && opts[i].Name < opts[j].Name) }) return opts } diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index 15216a35db0..7df7b059e1f 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -1,6 +1,7 @@ package sqlstore import ( + "fmt" "strings" "time" @@ -203,16 +204,17 @@ func GetDashboard(query *models.GetDashboardQuery) error { } type DashboardSearchProjection struct { - Id int64 - Uid string + ID int64 `xorm:"id"` + UID string `xorm:"uid"` Title string Slug string Term string IsFolder bool - FolderId int64 - FolderUid string + FolderID int64 `xorm:"folder_id"` + FolderUID string `xorm:"folder_uid"` FolderSlug string FolderTitle string + SortMeta int64 } func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { @@ -226,7 +228,9 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear }, } - filters = append(filters, query.Filters...) + for _, filter := range query.Sort.Filter { + filters = append(filters, filter) + } if query.OrgId != 0 { filters = append(filters, searchstore.OrgFilter{OrgId: query.OrgId}) @@ -307,27 +311,31 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard hits := make(map[int64]*search.Hit) for _, item := range res { - hit, exists := hits[item.Id] + hit, exists := hits[item.ID] if !exists { hit = &search.Hit{ - Id: item.Id, - Uid: item.Uid, + ID: item.ID, + UID: item.UID, Title: item.Title, - Uri: "db/" + item.Slug, - Url: models.GetDashboardFolderUrl(item.IsFolder, item.Uid, item.Slug), + URI: "db/" + item.Slug, + URL: models.GetDashboardFolderUrl(item.IsFolder, item.UID, item.Slug), Type: getHitType(item), - FolderId: item.FolderId, - FolderUid: item.FolderUid, + FolderID: item.FolderID, + FolderUID: item.FolderUID, FolderTitle: item.FolderTitle, Tags: []string{}, } - if item.FolderId > 0 { - hit.FolderUrl = models.GetFolderUrl(item.FolderUid, item.FolderSlug) + if item.FolderID > 0 { + hit.FolderURL = models.GetFolderUrl(item.FolderUID, item.FolderSlug) + } + + if query.Sort.MetaName != "" { + hit.SortMeta = strings.TrimSpace(fmt.Sprintf("%d %s", item.SortMeta, query.Sort.MetaName)) } query.Result = append(query.Result, hit) - hits[item.Id] = hit + hits[item.ID] = hit } if len(item.Term) > 0 { hit.Tags = append(hit.Tags, item.Term) diff --git a/pkg/services/sqlstore/dashboard_folder_test.go b/pkg/services/sqlstore/dashboard_folder_test.go index e19e37ec968..572014b3829 100644 --- a/pkg/services/sqlstore/dashboard_folder_test.go +++ b/pkg/services/sqlstore/dashboard_folder_test.go @@ -33,8 +33,8 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 2) - So(query.Result[0].Id, ShouldEqual, folder.Id) - So(query.Result[1].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, folder.Id) + So(query.Result[1].ID, ShouldEqual, dashInRoot.Id) }) }) @@ -57,7 +57,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 1) - So(query.Result[0].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, dashInRoot.Id) }) Convey("when the user is given permission", func() { @@ -75,8 +75,8 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 2) - So(query.Result[0].Id, ShouldEqual, folder.Id) - So(query.Result[1].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, folder.Id) + So(query.Result[1].ID, ShouldEqual, dashInRoot.Id) }) }) @@ -94,8 +94,8 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 2) - So(query.Result[0].Id, ShouldEqual, folder.Id) - So(query.Result[1].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, folder.Id) + So(query.Result[1].ID, ShouldEqual, dashInRoot.Id) }) }) }) @@ -116,7 +116,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 1) - So(query.Result[0].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, dashInRoot.Id) }) Convey("when the user is given permission to child", func() { @@ -128,8 +128,8 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 2) - So(query.Result[0].Id, ShouldEqual, childDash.Id) - So(query.Result[1].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, childDash.Id) + So(query.Result[1].ID, ShouldEqual, dashInRoot.Id) }) }) @@ -147,9 +147,9 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 3) - So(query.Result[0].Id, ShouldEqual, folder.Id) - So(query.Result[1].Id, ShouldEqual, childDash.Id) - So(query.Result[2].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, folder.Id) + So(query.Result[1].ID, ShouldEqual, childDash.Id) + So(query.Result[2].ID, ShouldEqual, dashInRoot.Id) }) }) }) @@ -171,10 +171,10 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 4) - So(query.Result[0].Id, ShouldEqual, folder1.Id) - So(query.Result[1].Id, ShouldEqual, folder2.Id) - So(query.Result[2].Id, ShouldEqual, childDash1.Id) - So(query.Result[3].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, folder1.Id) + So(query.Result[1].ID, ShouldEqual, folder2.Id) + So(query.Result[2].ID, ShouldEqual, childDash1.Id) + So(query.Result[3].ID, ShouldEqual, dashInRoot.Id) }) }) @@ -197,7 +197,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 1) - So(query.Result[0].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, dashInRoot.Id) }) }) Convey("and a dashboard is moved from folder with acl to the folder without an acl", func() { @@ -212,10 +212,10 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 4) - So(query.Result[0].Id, ShouldEqual, folder2.Id) - So(query.Result[1].Id, ShouldEqual, childDash1.Id) - So(query.Result[2].Id, ShouldEqual, childDash2.Id) - So(query.Result[3].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, folder2.Id) + So(query.Result[1].ID, ShouldEqual, childDash1.Id) + So(query.Result[2].ID, ShouldEqual, childDash2.Id) + So(query.Result[3].ID, ShouldEqual, dashInRoot.Id) }) }) @@ -236,10 +236,10 @@ func TestDashboardFolderDataAccess(t *testing.T) { err := SearchDashboards(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 4) - So(query.Result[0].Id, ShouldEqual, folder2.Id) - So(query.Result[1].Id, ShouldEqual, childDash1.Id) - So(query.Result[2].Id, ShouldEqual, childDash2.Id) - So(query.Result[3].Id, ShouldEqual, dashInRoot.Id) + So(query.Result[0].ID, ShouldEqual, folder2.Id) + So(query.Result[1].ID, ShouldEqual, childDash1.Id) + So(query.Result[2].ID, ShouldEqual, childDash2.Id) + So(query.Result[3].ID, ShouldEqual, dashInRoot.Id) }) }) }) @@ -267,8 +267,8 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 2) - So(query.Result[0].Id, ShouldEqual, folder1.Id) - So(query.Result[1].Id, ShouldEqual, folder2.Id) + So(query.Result[0].ID, ShouldEqual, folder1.Id) + So(query.Result[1].ID, ShouldEqual, folder2.Id) }) Convey("should have write access to all folders and dashboards", func() { @@ -320,8 +320,8 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 2) - So(query.Result[0].Id, ShouldEqual, folder1.Id) - So(query.Result[1].Id, ShouldEqual, folder2.Id) + So(query.Result[0].ID, ShouldEqual, folder1.Id) + So(query.Result[1].ID, ShouldEqual, folder2.Id) }) Convey("should have edit access to folders with default ACL", func() { @@ -352,7 +352,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 1) - So(query.Result[0].Id, ShouldEqual, folder2.Id) + So(query.Result[0].ID, ShouldEqual, folder2.Id) }) Convey("should have edit permission in folders", func() { @@ -416,7 +416,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 1) - So(query.Result[0].Id, ShouldEqual, folder1.Id) + So(query.Result[0].ID, ShouldEqual, folder1.Id) }) Convey("should not have edit permission in folders", func() { diff --git a/pkg/services/sqlstore/dashboard_test.go b/pkg/services/sqlstore/dashboard_test.go index bfdf903bead..21cf0ab4fe0 100644 --- a/pkg/services/sqlstore/dashboard_test.go +++ b/pkg/services/sqlstore/dashboard_test.go @@ -277,7 +277,7 @@ func TestDashboardDataAccess(t *testing.T) { So(len(query.Result), ShouldEqual, 1) hit := query.Result[0] So(hit.Type, ShouldEqual, search.DashHitFolder) - So(hit.Url, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug)) + So(hit.URL, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug)) So(hit.FolderTitle, ShouldEqual, "") }) @@ -337,12 +337,12 @@ func TestDashboardDataAccess(t *testing.T) { So(len(query.Result), ShouldEqual, 2) hit := query.Result[0] - So(hit.Id, ShouldEqual, savedDash.Id) - So(hit.Url, ShouldEqual, fmt.Sprintf("/d/%s/%s", savedDash.Uid, savedDash.Slug)) - So(hit.FolderId, ShouldEqual, savedFolder.Id) - So(hit.FolderUid, ShouldEqual, savedFolder.Uid) + So(hit.ID, ShouldEqual, savedDash.Id) + So(hit.URL, ShouldEqual, fmt.Sprintf("/d/%s/%s", savedDash.Uid, savedDash.Slug)) + So(hit.FolderID, ShouldEqual, savedFolder.Id) + So(hit.FolderUID, ShouldEqual, savedFolder.Uid) So(hit.FolderTitle, ShouldEqual, savedFolder.Title) - So(hit.FolderUrl, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug)) + So(hit.FolderURL, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug)) }) Convey("Should be able to search for dashboard by dashboard ids", func() { @@ -436,8 +436,8 @@ func TestDashboard_SortingOptions(t *testing.T) { require.NoError(t, err) require.Len(t, dashboards, 2) - assert.Equal(t, dashA.Id, dashboards[0].Id) - assert.Equal(t, dashB.Id, dashboards[1].Id) + assert.Equal(t, dashA.Id, dashboards[0].ID) + assert.Equal(t, dashB.Id, dashboards[1].ID) }) } diff --git a/pkg/services/sqlstore/searchstore/builder.go b/pkg/services/sqlstore/searchstore/builder.go index 9a24c932baf..6f084b62ba7 100644 --- a/pkg/services/sqlstore/searchstore/builder.go +++ b/pkg/services/sqlstore/searchstore/builder.go @@ -55,8 +55,15 @@ func (b *Builder) buildSelect() { dashboard.folder_id, folder.uid AS folder_uid, folder.slug AS folder_slug, - folder.title AS folder_title - FROM `) + folder.title AS folder_title `) + + for _, f := range b.Filters { + if f, ok := f.(FilterSelect); ok { + b.sql.WriteString(fmt.Sprintf(", %s", f.Select())) + } + } + + b.sql.WriteString(` FROM `) } func (b *Builder) applyFilters() (ordering string) { diff --git a/pkg/services/sqlstore/searchstore/filters.go b/pkg/services/sqlstore/searchstore/filters.go index ca7cebf92ee..fc20ee494bc 100644 --- a/pkg/services/sqlstore/searchstore/filters.go +++ b/pkg/services/sqlstore/searchstore/filters.go @@ -33,6 +33,10 @@ type FilterLeftJoin interface { LeftJoin() string } +type FilterSelect interface { + Select() string +} + const ( TypeFolder = "dash-folder" TypeDashboard = "dash-db" diff --git a/pkg/services/sqlstore/searchstore/search_test.go b/pkg/services/sqlstore/searchstore/search_test.go index 49eccb1fbae..9a799e40891 100644 --- a/pkg/services/sqlstore/searchstore/search_test.go +++ b/pkg/services/sqlstore/searchstore/search_test.go @@ -58,10 +58,10 @@ func TestBuilder_EqualResults_Basic(t *testing.T) { require.NoError(t, err) assert.Len(t, res, 1) - res[0].Uid = "" + res[0].UID = "" assert.EqualValues(t, []sqlstore.DashboardSearchProjection{ { - Id: 1, + ID: 1, Title: "A", Slug: "a", Term: "templated",