From 1feaf6df99e4c5a4cd117ba582d99eb81a82a317 Mon Sep 17 00:00:00 2001 From: Scott Lepper Date: Tue, 5 Nov 2024 17:02:13 -0500 Subject: [PATCH] search: add OR filter for kinds (#95915) search: add OR filter for kinds --- pkg/registry/apis/search/register.go | 3 +- pkg/services/unifiedSearch/service.go | 2 +- pkg/storage/unified/resource/index.go | 22 ++++++++++++-- pkg/storage/unified/resource/index_test.go | 30 ++++++++++++++----- pkg/storage/unified/resource/resource.pb.go | 8 ++--- pkg/storage/unified/resource/resource.proto | 2 +- .../resource/testdata/playlist-resource.json | 19 ++++++++++++ 7 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 pkg/storage/unified/resource/testdata/playlist-resource.json diff --git a/pkg/registry/apis/search/register.go b/pkg/registry/apis/search/register.go index c5162e5d963..5bd2e28a9a1 100644 --- a/pkg/registry/apis/search/register.go +++ b/pkg/registry/apis/search/register.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -106,7 +107,7 @@ func (b *SearchAPIBuilder) GetAPIRoutes() *builder.APIRoutes { searchRequest := &resource.SearchRequest{ Tenant: tenant, - Kind: queryParams.Get("kind"), + Kind: strings.Split(queryParams.Get("kind"), ","), QueryType: queryParams.Get("queryType"), Query: queryParams.Get("query"), Limit: int64(limit), diff --git a/pkg/services/unifiedSearch/service.go b/pkg/services/unifiedSearch/service.go index 014e6a7cdf9..72197c5a99c 100644 --- a/pkg/services/unifiedSearch/service.go +++ b/pkg/services/unifiedSearch/service.go @@ -165,7 +165,7 @@ func (s *StandardSearchService) doSearchQuery(ctx context.Context, qry Query, _ // will use stack id for cloud and org id for on-prem tenantId := request.GetNamespaceMapper(s.cfg)(orgID) - req := &resource.SearchRequest{Tenant: tenantId, Query: qry.Query, Limit: int64(qry.Limit), Offset: int64(qry.From)} + req := &resource.SearchRequest{Tenant: tenantId, Query: qry.Query, Limit: int64(qry.Limit), Offset: int64(qry.From), Kind: qry.Kind} res, err := s.resourceClient.Search(ctx, req) if err != nil { s.logger.Error("Failed to search resources", "error", err) diff --git a/pkg/storage/unified/resource/index.go b/pkg/storage/unified/resource/index.go index 3945d53b192..16f61e97a59 100644 --- a/pkg/storage/unified/resource/index.go +++ b/pkg/storage/unified/resource/index.go @@ -269,7 +269,19 @@ func (i *Index) Search(ctx context.Context, request *SearchRequest) (*IndexResul request.Limit = 10 } - query := bleve.NewQueryStringQuery(request.Query) + textQuery := bleve.NewQueryStringQuery(request.Query) + query := bleve.NewConjunctionQuery(textQuery) + + if len(request.Kind) > 0 { + // apply OR condition filter for each kind ( dashboard, folder, etc ) + orQuery := bleve.NewDisjunctionQuery() + for _, term := range request.Kind { + termQuery := bleve.NewTermQuery(term) + orQuery.AddQuery(termQuery) + } + query.AddQuery(orQuery) + } + req := bleve.NewSearchRequest(query) for _, group := range request.GroupBy { @@ -387,6 +399,12 @@ func fetchResourceTypes() []*ListOptions { Group: "folder.grafana.app", Resource: "folders", }, - }) + }, + &ListOptions{ + Key: &ResourceKey{ + Group: "dashboard.grafana.app", + Resource: "dashboards", + }, + }) return items } diff --git a/pkg/storage/unified/resource/index_test.go b/pkg/storage/unified/resource/index_test.go index b5b73b24a4e..4787be45a39 100644 --- a/pkg/storage/unified/resource/index_test.go +++ b/pkg/storage/unified/resource/index_test.go @@ -29,7 +29,7 @@ func TestIndexDashboard(t *testing.T) { require.NoError(t, err) assertCountEquals(t, index, 1) - assertSearchCountEquals(t, index, "*", 1) + assertSearchCountEquals(t, index, "*", nil, 1) } func TestIndexFolder(t *testing.T) { @@ -41,7 +41,7 @@ func TestIndexFolder(t *testing.T) { require.NoError(t, err) assertCountEquals(t, index, 1) - assertSearchCountEquals(t, index, "*", 1) + assertSearchCountEquals(t, index, "*", nil, 1) } func TestSearchFolder(t *testing.T) { @@ -54,7 +54,21 @@ func TestSearchFolder(t *testing.T) { require.NoError(t, err) assertCountEquals(t, index, 2) - assertSearchCountEquals(t, index, "Kind:Folder", 1) + assertSearchCountEquals(t, index, "*", []string{"folder"}, 1) +} + +func TestSearchDashboardsAndFoldersOnly(t *testing.T) { + dashboard := readTestData(t, "dashboard-resource.json") + folder := readTestData(t, "folder-resource.json") + playlist := readTestData(t, "playlist-resource.json") + list := &ListResponse{Items: []*ResourceWrapper{{Value: dashboard}, {Value: folder}, {Value: playlist}}} + index := newTestIndex(t, 1) + + err := index.writeBatch(testContext, list) + require.NoError(t, err) + + assertCountEquals(t, index, 3) + assertSearchCountEquals(t, index, "*", []string{"dashboard", "folder"}, 2) } func TestLookupNames(t *testing.T) { @@ -75,7 +89,7 @@ func TestLookupNames(t *testing.T) { for _, id := range chunk { query += `"` + id + `" ` } - assertSearchCountEquals(t, index, query, int64(len(chunk))) + assertSearchCountEquals(t, index, query, nil, int64(len(chunk))) } func TestIndexDashboardWithTags(t *testing.T) { @@ -88,8 +102,8 @@ func TestIndexDashboardWithTags(t *testing.T) { require.NoError(t, err) assertCountEquals(t, index, 2) - assertSearchCountEquals(t, index, "tag1", 2) - assertSearchCountEquals(t, index, "tag4", 1) + assertSearchCountEquals(t, index, "tag1", nil, 2) + assertSearchCountEquals(t, index, "tag4", nil, 1) assertSearchGroupCountEquals(t, index, "*", "tags", 4) assertSearchGroupCountEquals(t, index, "tag4", "tags", 3) } @@ -187,8 +201,8 @@ func assertCountEquals(t *testing.T, index *Index, expected uint64) { assert.Equal(t, expected, total) } -func assertSearchCountEquals(t *testing.T, index *Index, search string, expected int64) { - req := &SearchRequest{Query: search, Tenant: testTenant, Limit: expected + 1, Offset: 0} +func assertSearchCountEquals(t *testing.T, index *Index, search string, kind []string, expected int64) { + req := &SearchRequest{Query: search, Tenant: testTenant, Limit: expected + 1, Offset: 0, Kind: kind} start := time.Now() results, err := index.Search(testContext, req) require.NoError(t, err) diff --git a/pkg/storage/unified/resource/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go index 84862138129..6f79af359ff 100644 --- a/pkg/storage/unified/resource/resource.pb.go +++ b/pkg/storage/unified/resource/resource.pb.go @@ -1633,7 +1633,7 @@ type SearchRequest struct { QueryType string `protobuf:"bytes,2,opt,name=queryType,proto3" json:"queryType,omitempty"` Tenant string `protobuf:"bytes,3,opt,name=tenant,proto3" json:"tenant,omitempty"` // resource kind (playlists, dashboards, etc) - Kind string `protobuf:"bytes,4,opt,name=kind,proto3" json:"kind,omitempty"` + Kind []string `protobuf:"bytes,4,rep,name=kind,proto3" json:"kind,omitempty"` // pagination support Limit int64 `protobuf:"varint,5,opt,name=limit,proto3" json:"limit,omitempty"` Offset int64 `protobuf:"varint,6,opt,name=offset,proto3" json:"offset,omitempty"` @@ -1692,11 +1692,11 @@ func (x *SearchRequest) GetTenant() string { return "" } -func (x *SearchRequest) GetKind() string { +func (x *SearchRequest) GetKind() []string { if x != nil { return x.Kind } - return "" + return nil } func (x *SearchRequest) GetLimit() int64 { @@ -2916,7 +2916,7 @@ var file_resource_proto_rawDesc = []byte{ 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, - 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, + 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2b, diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto index bd5fcc54fa9..a0f61e02d80 100644 --- a/pkg/storage/unified/resource/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -330,7 +330,7 @@ message SearchRequest { string queryType = 2; string tenant = 3; // resource kind (playlists, dashboards, etc) - string kind = 4; + repeated string kind = 4; // pagination support int64 limit = 5; int64 offset = 6; diff --git a/pkg/storage/unified/resource/testdata/playlist-resource.json b/pkg/storage/unified/resource/testdata/playlist-resource.json new file mode 100644 index 00000000000..ef0ed38c67e --- /dev/null +++ b/pkg/storage/unified/resource/testdata/playlist-resource.json @@ -0,0 +1,19 @@ +{ + "kind": "Playlist", + "apiVersion": "playlist.grafana.app/v0alpha1", + "metadata": { + "name": "ae2ntrqxefvnke", + "namespace": "default", + "uid": "playlist-1", + "creationTimestamp": "2024-11-01T19:42:22Z", + "annotations": { + "grafana.app/createdBy": "user:1", + "grafana.app/originName": "SQL", + "grafana.app/originPath": "15", + "grafana.app/originTimestamp": "2024-11-01T19:42:22Z" + } + }, + "spec": { + "title": "test-us-playlist" + } +} \ No newline at end of file