From f0f33733a59ca1fb01bdf8e021d665a6de28214d Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Thu, 19 May 2022 12:46:18 -0400 Subject: [PATCH] Search: Add DashboardIndexExtender interface (#49045) Co-authored-by: Alexander Emelin --- pkg/services/searchV2/bluge.go | 31 ++++- pkg/services/searchV2/extender.go | 48 ++++++++ pkg/services/searchV2/index.go | 19 ++- pkg/services/searchV2/index_test.go | 119 +++++++++++++++++-- pkg/services/searchV2/service.go | 15 ++- pkg/services/searchV2/stub.go | 4 + pkg/services/searchV2/testdata/sort-asc.txt | 22 ++++ pkg/services/searchV2/testdata/sort-desc.txt | 22 ++++ pkg/services/searchV2/types.go | 1 + 9 files changed, 261 insertions(+), 20 deletions(-) create mode 100644 pkg/services/searchV2/extender.go create mode 100644 pkg/services/searchV2/testdata/sort-asc.txt create mode 100644 pkg/services/searchV2/testdata/sort-desc.txt diff --git a/pkg/services/searchV2/bluge.go b/pkg/services/searchV2/bluge.go index 7c50005fc9f..0aca30553ed 100644 --- a/pkg/services/searchV2/bluge.go +++ b/pkg/services/searchV2/bluge.go @@ -35,7 +35,7 @@ const ( documentFieldInternalID = "__internal_id" // only for migrations! (indexed as a string) ) -func initIndex(dashboards []dashboard, logger log.Logger) (*bluge.Reader, *bluge.Writer, error) { +func initIndex(dashboards []dashboard, logger log.Logger, extendDoc ExtendDashboardFunc) (*bluge.Reader, *bluge.Writer, error) { writer, err := bluge.OpenWriter(bluge.InMemoryOnlyConfig()) if err != nil { return nil, nil, fmt.Errorf("error opening writer: %v", err) @@ -74,6 +74,9 @@ func initIndex(dashboards []dashboard, logger log.Logger) (*bluge.Reader, *bluge continue } doc := getFolderDashboardDoc(dash) + if err := extendDoc(dash.uid, doc); err != nil { + return nil, nil, err + } batch.Insert(doc) if err := flushIfRequired(false); err != nil { return nil, nil, err @@ -93,6 +96,9 @@ func initIndex(dashboards []dashboard, logger log.Logger) (*bluge.Reader, *bluge folderUID := folderIdLookup[dash.folderID] location := folderUID doc := getNonFolderDashboardDoc(dash, location) + if err := extendDoc(dash.uid, doc); err != nil { + return nil, nil, err + } batch.Insert(doc) if err := flushIfRequired(false); err != nil { return nil, nil, err @@ -186,8 +192,6 @@ func getNonFolderDashboardDoc(dash dashboard, location string) *bluge.Document { } } - // TODO: enterprise, add dashboard sorting fields - return doc } @@ -313,7 +317,7 @@ func getDashboardPanelIDs(reader *bluge.Reader, dashboardUID string) ([]string, } //nolint: gocyclo -func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader, filter ResourceFilter, q DashboardQuery) *backend.DataResponse { +func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader, filter ResourceFilter, q DashboardQuery, extender QueryExtender) *backend.DataResponse { response := &backend.DataResponse{} // Folder listing structure @@ -419,8 +423,10 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader, } req.WithStandardAggregations() - // SortBy([]string{"-_score", "name"}) - // req.SortBy([]string{documentFieldName}) + // Field must be .Sortable() for sort to work with it. + if q.Sort != "" { + req.SortBy([]string{q.Sort}) + } for _, t := range q.Facet { lim := t.Limit @@ -475,6 +481,9 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader, frame.Fields = append(frame.Fields, fScore, fExplain) } + fieldLen := 0 + ext := extender.GetFramer(frame) + locationItems := make(map[string]bool, 50) // iterate through the document matches @@ -521,6 +530,8 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader, ds_uids = append(ds_uids, string(value)) case documentFieldTag: tags = append(tags, string(value)) + default: + return ext(field, value) } return true }) @@ -571,6 +582,14 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader, } } + // extend fields to match the longest field + fieldLen++ + for _, f := range frame.Fields { + if fieldLen > f.Len() { + f.Extend(fieldLen - f.Len()) + } + } + // load the next document match match, err = documentMatchIterator.Next() } diff --git a/pkg/services/searchV2/extender.go b/pkg/services/searchV2/extender.go new file mode 100644 index 00000000000..e046518fce3 --- /dev/null +++ b/pkg/services/searchV2/extender.go @@ -0,0 +1,48 @@ +package searchV2 + +import ( + "github.com/blugelabs/bluge" + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +type ExtendDashboardFunc func(uid string, doc *bluge.Document) error +type FramerFunc func(field string, value []byte) bool + +type QueryExtender interface { + GetFramer(frame *data.Frame) FramerFunc +} + +type DocumentExtender interface { + GetDashboardExtender(orgID int64, uids ...string) ExtendDashboardFunc +} + +type DashboardIndexExtender interface { + GetDocumentExtender() DocumentExtender + GetQueryExtender() QueryExtender +} + +type NoopExtender struct{} + +func (n NoopExtender) GetDocumentExtender() DocumentExtender { + return &NoopDocumentExtender{} +} + +func (n NoopExtender) GetQueryExtender() QueryExtender { + return &NoopQueryExtender{} +} + +type NoopDocumentExtender struct{} + +func (n NoopDocumentExtender) GetDashboardExtender(_ int64, _ ...string) ExtendDashboardFunc { + return func(uid string, doc *bluge.Document) error { + return nil + } +} + +type NoopQueryExtender struct{} + +func (n NoopQueryExtender) GetFramer(_ *data.Frame) FramerFunc { + return func(field string, value []byte) bool { + return true + } +} diff --git a/pkg/services/searchV2/index.go b/pkg/services/searchV2/index.go index 878943d74bf..815415a43fe 100644 --- a/pkg/services/searchV2/index.go +++ b/pkg/services/searchV2/index.go @@ -53,9 +53,10 @@ type dashboardIndex struct { eventStore eventStore logger log.Logger buildSignals chan int64 + extender DocumentExtender } -func newDashboardIndex(dashLoader dashboardLoader, evStore eventStore) *dashboardIndex { +func newDashboardIndex(dashLoader dashboardLoader, evStore eventStore, extender DocumentExtender) *dashboardIndex { return &dashboardIndex{ loader: dashLoader, eventStore: evStore, @@ -63,6 +64,7 @@ func newDashboardIndex(dashLoader dashboardLoader, evStore eventStore) *dashboar perOrgWriter: map[int64]*bluge.Writer{}, logger: log.New("dashboardIndex"), buildSignals: make(chan int64), + extender: extender, } } @@ -211,7 +213,8 @@ func (i *dashboardIndex) buildOrgIndex(ctx context.Context, orgID int64) (int, e orgSearchIndexLoadTime := time.Since(started) i.logger.Info("Finish loading org dashboards", "elapsed", orgSearchIndexLoadTime, "orgId", orgID) - reader, writer, err := initIndex(dashboards, i.logger) + dashboardExtender := i.extender.GetDashboardExtender(orgID) + reader, writer, err := initIndex(dashboards, i.logger, dashboardExtender) if err != nil { return 0, fmt.Errorf("error initializing index: %w", err) } @@ -350,7 +353,7 @@ func (i *dashboardIndex) applyDashboardEvent(ctx context.Context, orgID int64, d if len(dbDashboards) == 0 { newReader, err = i.removeDashboard(writer, reader, dashboardUID) } else { - newReader, err = i.updateDashboard(writer, reader, dbDashboards[0]) + newReader, err = i.updateDashboard(orgID, writer, reader, dbDashboards[0]) } if err != nil { return err @@ -389,12 +392,17 @@ func stringInSlice(str string, slice []string) bool { return false } -func (i *dashboardIndex) updateDashboard(writer *bluge.Writer, reader *bluge.Reader, dash dashboard) (*bluge.Reader, error) { +func (i *dashboardIndex) updateDashboard(orgID int64, writer *bluge.Writer, reader *bluge.Reader, dash dashboard) (*bluge.Reader, error) { batch := bluge.NewBatch() + extendDoc := i.extender.GetDashboardExtender(orgID, dash.uid) + var doc *bluge.Document if dash.isFolder { doc = getFolderDashboardDoc(dash) + if err := extendDoc(dash.uid, doc); err != nil { + return nil, err + } } else { var folderUID string if dash.folderID == 0 { @@ -409,6 +417,9 @@ func (i *dashboardIndex) updateDashboard(writer *bluge.Writer, reader *bluge.Rea location := folderUID doc = getNonFolderDashboardDoc(dash, location) + if err := extendDoc(dash.uid, doc); err != nil { + return nil, err + } var actualPanelIDs []string diff --git a/pkg/services/searchV2/index_test.go b/pkg/services/searchV2/index_test.go index 67fe43d80f5..59e932fff58 100644 --- a/pkg/services/searchV2/index_test.go +++ b/pkg/services/searchV2/index_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/services/store" "github.com/blugelabs/bluge" + "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/experimental" "github.com/stretchr/testify/require" ) @@ -33,28 +34,40 @@ var testDisallowAllFilter = func(uid string) bool { return false } +var testOrgID int64 = 1 + var update = flag.Bool("update", false, "update golden files") func initTestIndexFromDashes(t *testing.T, dashboards []dashboard) (*dashboardIndex, *bluge.Reader, *bluge.Writer) { + t.Helper() + return initTestIndexFromDashesExtended(t, dashboards, &NoopDocumentExtender{}) +} + +func initTestIndexFromDashesExtended(t *testing.T, dashboards []dashboard, extender DocumentExtender) (*dashboardIndex, *bluge.Reader, *bluge.Writer) { t.Helper() dashboardLoader := &testDashboardLoader{ dashboards: dashboards, } - index := newDashboardIndex(dashboardLoader, &store.MockEntityEventsService{}) + index := newDashboardIndex(dashboardLoader, &store.MockEntityEventsService{}, extender) require.NotNil(t, index) - numDashboards, err := index.buildOrgIndex(context.Background(), 1) + numDashboards, err := index.buildOrgIndex(context.Background(), testOrgID) require.NoError(t, err) require.Equal(t, len(dashboardLoader.dashboards), numDashboards) - reader, ok := index.getOrgReader(1) + reader, ok := index.getOrgReader(testOrgID) require.True(t, ok) - writer, ok := index.getOrgWriter(1) + writer, ok := index.getOrgWriter(testOrgID) require.True(t, ok) return index, reader, writer } func checkSearchResponse(t *testing.T, fileName string, reader *bluge.Reader, filter ResourceFilter, query DashboardQuery) { t.Helper() - resp := doSearchQuery(context.Background(), testLogger, reader, filter, query) + checkSearchResponseExtended(t, fileName, reader, filter, query, &NoopQueryExtender{}) +} + +func checkSearchResponseExtended(t *testing.T, fileName string, reader *bluge.Reader, filter ResourceFilter, query DashboardQuery, extender QueryExtender) { + t.Helper() + resp := doSearchQuery(context.Background(), testLogger, reader, filter, query, extender) goldenFile := filepath.Join("testdata", fileName) err := experimental.CheckGoldenDataResponse(goldenFile, resp, *update) require.NoError(t, err) @@ -108,7 +121,7 @@ func TestDashboardIndexUpdates(t *testing.T) { t.Run("dashboard-create", func(t *testing.T) { index, reader, writer := initTestIndexFromDashes(t, testDashboards) - newReader, err := index.updateDashboard(writer, reader, dashboard{ + newReader, err := index.updateDashboard(testOrgID, writer, reader, dashboard{ id: 3, uid: "3", info: &extract.DashboardInfo{ @@ -125,7 +138,7 @@ func TestDashboardIndexUpdates(t *testing.T) { t.Run("dashboard-update", func(t *testing.T) { index, reader, writer := initTestIndexFromDashes(t, testDashboards) - newReader, err := index.updateDashboard(writer, reader, dashboard{ + newReader, err := index.updateDashboard(testOrgID, writer, reader, dashboard{ id: 2, uid: "2", info: &extract.DashboardInfo{ @@ -140,6 +153,98 @@ func TestDashboardIndexUpdates(t *testing.T) { }) } +var testSortDashboards = []dashboard{ + { + id: 1, + uid: "1", + info: &extract.DashboardInfo{ + Title: "a-test", + }, + }, + { + id: 2, + uid: "2", + info: &extract.DashboardInfo{ + Title: "z-test", + }, + }, +} + +type testExtender struct { + documentExtender DocumentExtender + queryExtender QueryExtender +} + +func (t *testExtender) GetDocumentExtender() DocumentExtender { + return t.documentExtender +} + +func (t *testExtender) GetQueryExtender() QueryExtender { + return t.queryExtender +} + +type testDocumentExtender struct { + ExtendDashboardFunc ExtendDashboardFunc +} + +func (t *testDocumentExtender) GetDashboardExtender(_ int64, _ ...string) ExtendDashboardFunc { + return t.ExtendDashboardFunc +} + +type testQueryExtender struct { + getFramer func(frame *data.Frame) FramerFunc +} + +func (t *testQueryExtender) GetFramer(frame *data.Frame) FramerFunc { + return t.getFramer(frame) +} + +func TestDashboardIndexSort(t *testing.T) { + var i float64 + extender := &testExtender{ + documentExtender: &testDocumentExtender{ + ExtendDashboardFunc: func(uid string, doc *bluge.Document) error { + doc.AddField(bluge.NewNumericField("test", i).StoreValue().Sortable()) + i++ + return nil + }, + }, + queryExtender: &testQueryExtender{ + getFramer: func(frame *data.Frame) FramerFunc { + testNum := data.NewFieldFromFieldType(data.FieldTypeFloat64, 0) + testNum.Name = "test num" + frame.Fields = append( + frame.Fields, + testNum, + ) + return func(field string, value []byte) bool { + if field == "test" { + if num, err := bluge.DecodeNumericFloat64(value); err == nil { + testNum.Append(num) + return true + } + } + return true + } + }, + }, + } + + t.Run("sort-asc", func(t *testing.T) { + _, reader, _ := initTestIndexFromDashesExtended(t, testSortDashboards, extender.GetDocumentExtender()) + checkSearchResponseExtended(t, filepath.Base(t.Name())+".txt", reader, testAllowAllFilter, + DashboardQuery{Query: "*", Sort: "test"}, extender.GetQueryExtender(), + ) + }) + + t.Run("sort-desc", func(t *testing.T) { + _, reader, _ := initTestIndexFromDashesExtended(t, testSortDashboards, extender.GetDocumentExtender()) + checkSearchResponseExtended(t, filepath.Base(t.Name())+".txt", reader, testAllowAllFilter, + DashboardQuery{Query: "*", Sort: "-test"}, extender.GetQueryExtender(), + ) + }) +} + var testPrefixDashboards = []dashboard{ { id: 1, diff --git a/pkg/services/searchV2/service.go b/pkg/services/searchV2/service.go index a3b17ba1811..2c4341f4961 100644 --- a/pkg/services/searchV2/service.go +++ b/pkg/services/searchV2/service.go @@ -26,10 +26,12 @@ type StandardSearchService struct { logger log.Logger dashboardIndex *dashboardIndex + extender DashboardIndexExtender } func ProvideService(cfg *setting.Cfg, sql *sqlstore.SQLStore, entityEventStore store.EntityEventsService, ac accesscontrol.AccessControl) SearchService { - return &StandardSearchService{ + extender := &NoopExtender{} + s := &StandardSearchService{ cfg: cfg, sql: sql, ac: ac, @@ -37,9 +39,11 @@ func ProvideService(cfg *setting.Cfg, sql *sqlstore.SQLStore, entityEventStore s sql: sql, ac: ac, }, - dashboardIndex: newDashboardIndex(newSQLDashboardLoader(sql), entityEventStore), + dashboardIndex: newDashboardIndex(newSQLDashboardLoader(sql), entityEventStore, extender.GetDocumentExtender()), logger: log.New("searchV2"), + extender: extender, } + return s } func (s *StandardSearchService) IsDisabled() bool { @@ -53,6 +57,11 @@ func (s *StandardSearchService) Run(ctx context.Context) error { return s.dashboardIndex.run(ctx) } +func (s *StandardSearchService) RegisterDashboardIndexExtender(ext DashboardIndexExtender) { + s.extender = ext + s.dashboardIndex.extender = ext.GetDocumentExtender() +} + func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backend.User, orgId int64) (*models.SignedInUser, error) { // TODO: get user & user's permissions from the request context @@ -123,5 +132,5 @@ func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *back return rsp } - return doSearchQuery(ctx, s.logger, reader, filter, q) + return doSearchQuery(ctx, s.logger, reader, filter, q, s.extender.GetQueryExtender()) } diff --git a/pkg/services/searchV2/stub.go b/pkg/services/searchV2/stub.go index b02e34695ea..9d0dc6a105d 100644 --- a/pkg/services/searchV2/stub.go +++ b/pkg/services/searchV2/stub.go @@ -29,6 +29,10 @@ func (s *stubSearchService) DoDashboardQuery(ctx context.Context, user *backend. return rsp } +func (s *stubSearchService) RegisterDashboardIndexExtender(ext DashboardIndexExtender) { + // noop +} + func (s *stubSearchService) Run(_ context.Context) error { return nil } diff --git a/pkg/services/searchV2/testdata/sort-asc.txt b/pkg/services/searchV2/testdata/sort-asc.txt new file mode 100644 index 00000000000..4c812524c57 --- /dev/null +++ b/pkg/services/searchV2/testdata/sort-asc.txt @@ -0,0 +1,22 @@ +🌟 This was machine generated. Do not edit. 🌟 + +Frame[0] { + "type": "search-results", + "custom": { + "count": 2 + } +} +Name: Query results +Dimensions: 9 Fields by 2 Rows ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ +| Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num | +| Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +| Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []float64 | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ +| dashboard | 1 | a-test | | /d/1/ | null | null | | 0 | +| dashboard | 2 | z-test | | /d/2/ | null | null | | 1 | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ + + +====== TEST DATA RESPONSE (arrow base64) ====== +FRAME=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 diff --git a/pkg/services/searchV2/testdata/sort-desc.txt b/pkg/services/searchV2/testdata/sort-desc.txt new file mode 100644 index 00000000000..846d1a74b22 --- /dev/null +++ b/pkg/services/searchV2/testdata/sort-desc.txt @@ -0,0 +1,22 @@ +🌟 This was machine generated. Do not edit. 🌟 + +Frame[0] { + "type": "search-results", + "custom": { + "count": 2 + } +} +Name: Query results +Dimensions: 9 Fields by 2 Rows ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ +| Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: test num | +| Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | +| Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []float64 | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ +| dashboard | 2 | z-test | | /d/2/ | null | null | | 3 | +| dashboard | 1 | a-test | | /d/1/ | null | null | | 2 | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+-----------------+ + + +====== TEST DATA RESPONSE (arrow base64) ====== +FRAME=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 diff --git a/pkg/services/searchV2/types.go b/pkg/services/searchV2/types.go index adb64f21eca..29153079e6c 100644 --- a/pkg/services/searchV2/types.go +++ b/pkg/services/searchV2/types.go @@ -33,4 +33,5 @@ type DashboardQuery struct { type SearchService interface { registry.BackgroundService DoDashboardQuery(ctx context.Context, user *backend.User, orgId int64, query DashboardQuery) *backend.DataResponse + RegisterDashboardIndexExtender(ext DashboardIndexExtender) }