diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage.go b/pkg/services/folder/folderimpl/folder_unifiedstorage.go index ae1f579efd0..d32641d68f2 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage.go @@ -240,9 +240,9 @@ func (s *Service) getFolderByTitleFromApiServer(ctx context.Context, orgID int64 Key: folderkey, Fields: []*resource.Requirement{ { - Key: resource.SEARCH_FIELD_TITLE_SORT, + Key: resource.SEARCH_FIELD_TITLE_PHRASE, Operator: string(selection.In), - Values: []string{strings.ToLower(title)}, + Values: []string{title}, }, }, Labels: []*resource.Requirement{}, diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go index a19c83f209e..7377c18bc2f 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage_test.go @@ -567,7 +567,7 @@ func (r resourceClientMock) Search(ctx context.Context, in *resource.ResourceSea } if len(in.Options.Fields) > 0 && - in.Options.Fields[0].Key == resource.SEARCH_FIELD_TITLE_SORT && + in.Options.Fields[0].Key == resource.SEARCH_FIELD_TITLE_PHRASE && in.Options.Fields[0].Operator == "in" && len(in.Options.Fields[0].Values) > 0 && in.Options.Fields[0].Values[0] == "foo" { diff --git a/pkg/storage/unified/resource/document.go b/pkg/storage/unified/resource/document.go index ce5ecdf0d16..a868719263d 100644 --- a/pkg/storage/unified/resource/document.go +++ b/pkg/storage/unified/resource/document.go @@ -63,7 +63,7 @@ type IndexableDocument struct { Title string `json:"title,omitempty"` // internal sort field for title ( don't set this directly ) - TitleSort string `json:"title_sort,omitempty"` + TitlePhrase string `json:"title_phrase,omitempty"` // A generic description -- helpful in global search Description string `json:"description,omitempty"` @@ -163,15 +163,15 @@ func NewIndexableDocument(key *ResourceKey, rv int64, obj utils.GrafanaMetaAcces } } doc := &IndexableDocument{ - Key: key, - RV: rv, - Name: key.Name, - Title: title, // We always want *something* to display - TitleSort: strings.ToLower(title), // Lowercase for case-insensitive sorting - Labels: obj.GetLabels(), - Folder: obj.GetFolder(), - CreatedBy: obj.GetCreatedBy(), - UpdatedBy: obj.GetUpdatedBy(), + Key: key, + RV: rv, + Name: key.Name, + Title: title, // We always want *something* to display + TitlePhrase: strings.ToLower(title), // Lowercase for case-insensitive sorting + Labels: obj.GetLabels(), + Folder: obj.GetFolder(), + CreatedBy: obj.GetCreatedBy(), + UpdatedBy: obj.GetUpdatedBy(), } doc.RepoInfo, _ = obj.GetRepositoryInfo() ts := obj.GetCreationTimestamp() @@ -253,7 +253,7 @@ const SEARCH_FIELD_NAMESPACE = "namespace" const SEARCH_FIELD_NAME = "name" const SEARCH_FIELD_RV = "rv" const SEARCH_FIELD_TITLE = "title" -const SEARCH_FIELD_TITLE_SORT = "title_sort" +const SEARCH_FIELD_TITLE_PHRASE = "title_phrase" // filtering/sorting on title by full phrase const SEARCH_FIELD_DESCRIPTION = "description" const SEARCH_FIELD_TAGS = "tags" const SEARCH_FIELD_LABELS = "labels" // All labels, not a specific one diff --git a/pkg/storage/unified/resource/document_test.go b/pkg/storage/unified/resource/document_test.go index 21ef906c6c0..593d1fbffd9 100644 --- a/pkg/storage/unified/resource/document_test.go +++ b/pkg/storage/unified/resource/document_test.go @@ -35,7 +35,7 @@ func TestStandardDocumentBuilder(t *testing.T) { }, "rv": 10, "title": "test playlist unified storage", - "title_sort": "test playlist unified storage", + "title_phrase": "test playlist unified storage", "created": 1717236672000, "createdBy": "user:ABC", "updatedBy": "user:XYZ", diff --git a/pkg/storage/unified/resource/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go index 651f9a5e4ba..9a41f4c4e5a 100644 --- a/pkg/storage/unified/resource/resource.pb.go +++ b/pkg/storage/unified/resource/resource.pb.go @@ -1284,12 +1284,12 @@ type ListOptions struct { // Group+Namespace+Resource (not name) Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // (best effort) Match label - // Allowed to send more results than actually match because the filter will be appled - // to the resutls agin in the client. That time with the full field selector + // Allowed to send more results than actually match because the filter will be applied + // to the results again in the client. That time with the full field selector Labels []*Requirement `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty"` // (best effort) fields matcher - // Allowed to send more results than actually match because the filter will be appled - // to the resutls agin in the client. That time with the full field selector + // Allowed to send more results than actually match because the filter will be applied + // to the results again in the client. That time with the full field selector Fields []*Requirement `protobuf:"bytes,3,rep,name=fields,proto3" json:"fields,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto index 005b7e6ab4e..ae7d067ff5a 100644 --- a/pkg/storage/unified/resource/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -209,13 +209,13 @@ message ListOptions { ResourceKey key = 1; // (best effort) Match label - // Allowed to send more results than actually match because the filter will be appled - // to the resutls agin in the client. That time with the full field selector + // Allowed to send more results than actually match because the filter will be applied + // to the results again in the client. That time with the full field selector repeated Requirement labels = 2; // (best effort) fields matcher - // Allowed to send more results than actually match because the filter will be appled - // to the resutls agin in the client. That time with the full field selector + // Allowed to send more results than actually match because the filter will be applied + // to the results again in the client. That time with the full field selector repeated Requirement fields = 3; } diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index 145bd025cd7..bf9748cf5bc 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -684,9 +684,11 @@ func getSortFields(req *resource.ResourceSearchRequest) []string { // fields that we went to sort by the full text var textSortFields = map[string]string{ - resource.SEARCH_FIELD_TITLE: resource.SEARCH_FIELD_TITLE + "_sort", + resource.SEARCH_FIELD_TITLE: resource.SEARCH_FIELD_TITLE_PHRASE, } +const lowerCase = "phrase" + // Convert a "requirement" into a bleve query func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *resource.ErrorResult) { switch selection.Operator(req.Operator) { @@ -696,14 +698,14 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r } if len(req.Values[0]) == 1 { - q := query.NewMatchQuery(req.Values[0]) + q := query.NewMatchQuery(filterValue(req.Key, req.Values[0])) q.FieldVal = prefix + req.Key return q, nil } conjuncts := []query.Query{} for _, v := range req.Values { - q := query.NewMatchQuery(v) + q := query.NewMatchQuery(filterValue(req.Key, v)) q.FieldVal = prefix + req.Key conjuncts = append(conjuncts, q) } @@ -720,14 +722,14 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r return query.NewMatchAllQuery(), nil } if len(req.Values) == 1 { - q := query.NewMatchQuery(req.Values[0]) + q := query.NewMatchQuery(filterValue(req.Key, req.Values[0])) q.FieldVal = prefix + req.Key return q, nil } disjuncts := []query.Query{} for _, v := range req.Values { - q := query.NewMatchQuery(v) + q := query.NewMatchQuery(filterValue(req.Key, v)) q.FieldVal = prefix + req.Key disjuncts = append(disjuncts, q) } @@ -739,7 +741,7 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r var mustNotQueries []query.Query for _, value := range req.Values { - mustNotQueries = append(mustNotQueries, bleve.NewMatchQuery(value)) + mustNotQueries = append(mustNotQueries, bleve.NewMatchQuery(filterValue(req.Key, value))) } boolQuery.AddMustNot(mustNotQueries...) @@ -754,6 +756,14 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r ) } +// filterValue will convert the value to lower case if the field is a phrase field +func filterValue(field string, v string) string { + if strings.HasSuffix(field, lowerCase) { + return strings.ToLower(v) + } + return v +} + func (b *bleveIndex) hitsToTable(ctx context.Context, selectFields []string, hits search.DocumentMatchCollection, explain bool) (*resource.ResourceTable, error) { _, span := b.tracing.Start(ctx, tracingPrexfixBleve+"hitsToTable") defer span.End() diff --git a/pkg/storage/unified/search/bleve_mappings.go b/pkg/storage/unified/search/bleve_mappings.go index a1ec7d9fd6b..027fd87b947 100644 --- a/pkg/storage/unified/search/bleve_mappings.go +++ b/pkg/storage/unified/search/bleve_mappings.go @@ -24,11 +24,12 @@ func getBleveDocMappings(_ resource.SearchableDocumentFields) *mapping.DocumentM } mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_NAME, nameMapping) - // for sorting by title - titleSortMapping := bleve.NewKeywordFieldMapping() - mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE_SORT, titleSortMapping) + // for filtering/sorting by title full phrase + titlePhraseMapping := bleve.NewKeywordFieldMapping() + mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE_PHRASE, titlePhraseMapping) // for searching by title + // TODO: do we still need this since we have SEARCH_FIELD_TITLE_PHRASE? titleSearchMapping := bleve.NewTextFieldMapping() mapper.AddFieldMappingsAt(resource.SEARCH_FIELD_TITLE, titleSearchMapping) diff --git a/pkg/storage/unified/search/bleve_test.go b/pkg/storage/unified/search/bleve_test.go index 4535b276c0a..66acc8d9a25 100644 --- a/pkg/storage/unified/search/bleve_test.go +++ b/pkg/storage/unified/search/bleve_test.go @@ -78,9 +78,9 @@ func TestBleveBackend(t *testing.T) { Group: "dashboard.grafana.app", Resource: "dashboards", }, - Title: "aaa (dash)", - TitleSort: "aaa (dash)", - Folder: "xxx", + Title: "aaa (dash)", + TitlePhrase: "aaa (dash)", + Folder: "xxx", Fields: map[string]any{ DASHBOARD_PANEL_TYPES: []string{"timeseries", "table"}, DASHBOARD_ERRORS_TODAY: 25, @@ -106,9 +106,9 @@ func TestBleveBackend(t *testing.T) { Group: "dashboard.grafana.app", Resource: "dashboards", }, - Title: "bbb (dash)", - TitleSort: "bbb (dash)", - Folder: "xxx", + Title: "bbb (dash)", + TitlePhrase: "bbb (dash)", + Folder: "xxx", Fields: map[string]any{ DASHBOARD_PANEL_TYPES: []string{"timeseries"}, DASHBOARD_ERRORS_TODAY: 40, @@ -134,10 +134,10 @@ func TestBleveBackend(t *testing.T) { Group: "dashboard.grafana.app", Resource: "dashboards", }, - Name: "ccc", - Title: "ccc (dash)", - TitleSort: "ccc (dash)", - Folder: "zzz", + Name: "ccc", + Title: "ccc (dash)", + TitlePhrase: "ccc (dash)", + Folder: "zzz", RepoInfo: &utils.ResourceRepositoryInfo{ Name: "repo2", Path: "path/in/repo2.yaml", @@ -332,8 +332,8 @@ func TestBleveBackend(t *testing.T) { Group: "folder.grafana.app", Resource: "folders", }, - Title: "zzz (folder)", - TitleSort: "zzz (folder)", + Title: "zzz (folder)", + TitlePhrase: "zzz (folder)", RepoInfo: &utils.ResourceRepositoryInfo{ Name: "repo-1", Path: "path/to/folder.json", @@ -349,8 +349,8 @@ func TestBleveBackend(t *testing.T) { Group: "folder.grafana.app", Resource: "folders", }, - Title: "yyy (folder)", - TitleSort: "yyy (folder)", + Title: "yyy (folder)", + TitlePhrase: "yyy (folder)", Labels: map[string]string{ "region": "west", }, diff --git a/pkg/storage/unified/search/testdata/doc/dashboard-aaa-out.json b/pkg/storage/unified/search/testdata/doc/dashboard-aaa-out.json index 04278a03fde..9fa0a53b2c1 100644 --- a/pkg/storage/unified/search/testdata/doc/dashboard-aaa-out.json +++ b/pkg/storage/unified/search/testdata/doc/dashboard-aaa-out.json @@ -8,7 +8,7 @@ "name": "aaa", "rv": 1234, "title": "Test title", - "title_sort": "test title", + "title_phrase": "test title", "description": "test description", "tags": [ "a", diff --git a/pkg/storage/unified/search/testdata/doc/folder-aaa-out.json b/pkg/storage/unified/search/testdata/doc/folder-aaa-out.json index 85809ce618f..86cb4f5df16 100644 --- a/pkg/storage/unified/search/testdata/doc/folder-aaa-out.json +++ b/pkg/storage/unified/search/testdata/doc/folder-aaa-out.json @@ -8,7 +8,7 @@ "name": "aaa", "rv": 1234, "title": "test-aaa", - "title_sort": "test-aaa", + "title_phrase": "test-aaa", "created": 1730490142000, "createdBy": "user:1", "repo": { diff --git a/pkg/storage/unified/search/testdata/doc/folder-bbb-out.json b/pkg/storage/unified/search/testdata/doc/folder-bbb-out.json index dec666e5d42..5d268564f5b 100644 --- a/pkg/storage/unified/search/testdata/doc/folder-bbb-out.json +++ b/pkg/storage/unified/search/testdata/doc/folder-bbb-out.json @@ -8,7 +8,7 @@ "name": "bbb", "rv": 1234, "title": "test-bbb", - "title_sort": "test-bbb", + "title_phrase": "test-bbb", "created": 1730490142000, "createdBy": "user:1", "repo": { diff --git a/pkg/storage/unified/search/testdata/doc/playlist-aaa-out.json b/pkg/storage/unified/search/testdata/doc/playlist-aaa-out.json index a4fc4117a9a..c0f12814338 100644 --- a/pkg/storage/unified/search/testdata/doc/playlist-aaa-out.json +++ b/pkg/storage/unified/search/testdata/doc/playlist-aaa-out.json @@ -8,7 +8,7 @@ "name": "aaa", "rv": 1234, "title": "Test AAA", - "title_sort": "test aaa", + "title_phrase": "test aaa", "created": 1731336353000, "createdBy": "user:t000000001", "repo": { diff --git a/pkg/storage/unified/search/testdata/doc/report-aaa-out.json b/pkg/storage/unified/search/testdata/doc/report-aaa-out.json index ad38a85e3c8..6b193267072 100644 --- a/pkg/storage/unified/search/testdata/doc/report-aaa-out.json +++ b/pkg/storage/unified/search/testdata/doc/report-aaa-out.json @@ -8,7 +8,7 @@ "name": "aaa", "rv": 1234, "title": "Test AAA", - "title_sort": "test aaa", + "title_phrase": "test aaa", "labels": { "grafana.app/deprecatedInternalID": "123" },