diff --git a/pkg/storage/unified/resource/document.go b/pkg/storage/unified/resource/document.go index d8e07a58e0f..4e528b96df0 100644 --- a/pkg/storage/unified/resource/document.go +++ b/pkg/storage/unified/resource/document.go @@ -103,7 +103,10 @@ type IndexableDocument struct { // Maintain a list of resource references. // Someday this will likely be part of https://github.com/grafana/gamma - References ResourceReferences `json:"reference,omitempty"` + References ResourceReferences `json:"references,omitempty"` + + // internal field for mapping references to kind ( don't set this directly ) + Reference map[string][]string `json:"reference,omitempty"` // map of kind to list of names // When the resource is managed by an upstream repository Manager *utils.ManagerProperties `json:"manager,omitempty"` @@ -121,6 +124,12 @@ func (m *IndexableDocument) UpdateCopyFields() *IndexableDocument { if m.Manager != nil { m.ManagedBy = fmt.Sprintf("%s:%s", m.Manager.Kind, m.Manager.Identity) } + + m.Reference = make(map[string][]string) + for _, ref := range m.References { + // Group and Version are ignored for now. This could be revisited. + m.Reference[ref.Kind] = append(m.Reference[ref.Kind], ref.Name) + } return m } diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index a73b0249fce..503bd7a8dfb 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -342,7 +342,6 @@ func (b *bleveIndex) BulkIndex(req *resource.BulkIndexRequest) error { return fmt.Errorf("missing document") } doc := item.Doc.UpdateCopyFields() - doc.References = nil // remove references (for now!) err := batch.Index(resource.SearchID(doc.Key), doc) if err != nil { diff --git a/pkg/storage/unified/search/bleve_mappings.go b/pkg/storage/unified/search/bleve_mappings.go index 3a8c4e75420..9f08c01bedc 100644 --- a/pkg/storage/unified/search/bleve_mappings.go +++ b/pkg/storage/unified/search/bleve_mappings.go @@ -137,6 +137,10 @@ func getBleveDocMappings(_ resource.SearchableDocumentFields) *mapping.DocumentM IncludeInAll: false, }) + referenceMapper := bleve.NewDocumentMapping() + referenceMapper.DefaultAnalyzer = keyword.Name + mapper.AddSubDocumentMapping("reference", referenceMapper) + labelMapper := bleve.NewDocumentMapping() mapper.AddSubDocumentMapping(resource.SEARCH_FIELD_LABELS, labelMapper) 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 964eb2fec41..5989ecb1804 100644 --- a/pkg/storage/unified/search/testdata/doc/dashboard-aaa-out.json +++ b/pkg/storage/unified/search/testdata/doc/dashboard-aaa-out.json @@ -40,7 +40,7 @@ ], "schema_version": 38 }, - "reference": [ + "references": [ { "relation": "depends-on", "group": "my-custom-plugin", diff --git a/pkg/storage/unified/testing/search_backend.go b/pkg/storage/unified/testing/search_backend.go index e42e3aae12e..505e1b1a43d 100644 --- a/pkg/storage/unified/testing/search_backend.go +++ b/pkg/storage/unified/testing/search_backend.go @@ -107,7 +107,7 @@ func runTestSearchBackendTotalDocs(t *testing.T, backend resource.SearchBackend, } func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix string) { - ctx := testutil.NewTestContext(t, time.Now().Add(5*time.Second)) + ctx := testutil.NewTestContext(t, time.Now().Add(50*time.Second)) ns := resource.NamespacedResource{ Namespace: nsPrefix + "-ns1", Group: "group", @@ -236,4 +236,100 @@ func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix require.NotNil(t, resp) require.Equal(t, int64(3), resp.TotalHits) // Both doc1, doc2, and doc3 should have doc now }) + + t.Run("Search by LibraryPanel reference", func(t *testing.T) { + // Build index with dashboards that have LibraryPanel references + index, err := backend.BuildIndex(ctx, ns, 3, 0, nil, func(index resource.ResourceIndex) (int64, error) { + err := index.BulkIndex(&resource.BulkIndexRequest{ + Items: []*resource.BulkIndexItem{ + { + Action: resource.ActionIndex, + Doc: &resource.IndexableDocument{ + Key: &resourcepb.ResourceKey{ + Namespace: ns.Namespace, + Group: ns.Group, + Resource: ns.Resource, + Name: "dash1", + }, + Title: "Dashboard with Library Panel 1", + References: resource.ResourceReferences{ + { + Relation: "depends-on", + Group: "dashboards.grafana.app", + Kind: "LibraryPanel", + Name: "lib-panel-1", + }, + }, + }, + }, + { + Action: resource.ActionIndex, + Doc: &resource.IndexableDocument{ + Key: &resourcepb.ResourceKey{ + Namespace: ns.Namespace, + Group: ns.Group, + Resource: ns.Resource, + Name: "dash2", + }, + Title: "Dashboard with Library Panel 2", + References: resource.ResourceReferences{ + { + Relation: "depends-on", + Group: "dashboards.grafana.app", + Kind: "LibraryPanel", + Name: "lib-panel-2", + }, + }, + }, + }, + { + Action: resource.ActionIndex, + Doc: &resource.IndexableDocument{ + Key: &resourcepb.ResourceKey{ + Namespace: ns.Namespace, + Group: ns.Group, + Resource: ns.Resource, + Name: "dash3", + }, + Title: "Dashboard without Library Panel", + }, + }, + }, + }) + require.NoError(t, err) + return int64(3), nil + }) + require.NoError(t, err) + require.NotNil(t, index) + + // Search for dashboards with specific LibraryPanel reference + resp, err := index.Search(ctx, nil, &resourcepb.ResourceSearchRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: ns.Namespace, + Group: ns.Group, + Resource: ns.Resource, + }, + Fields: []*resourcepb.Requirement{ + { + Key: "reference.LibraryPanel", + Operator: "=", + Values: []string{"lib-panel-1"}, + }, + }, + }, + Query: "", + Fields: []string{"title"}, + Limit: 10, + }, nil) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, int64(1), resp.TotalHits) // Only dash1 should have lib-panel-1 + + // Verify the result + require.Len(t, resp.Results.Rows, 1) + row := resp.Results.Rows[0] + require.Equal(t, "dash1", row.Key.Name) + require.Equal(t, "Dashboard with Library Panel 1", string(row.Cells[0])) // title field + }) }