From baa50c58d03b428883fe2b38451ff04a310ca4da Mon Sep 17 00:00:00 2001 From: Alexander Emelin Date: Tue, 17 May 2022 02:22:45 +0300 Subject: [PATCH] Search: use only bluge-based search (#48968) --- pkg/services/searchV2/bluge.go | 315 +++++++++++------- pkg/services/searchV2/index.go | 278 +++++++++------- pkg/services/searchV2/index_test.go | 181 ++++++---- pkg/services/searchV2/service.go | 228 +------------ .../searchV2/testdata/basic-filter.txt | 20 ++ .../searchV2/testdata/basic-search.txt | 21 ++ .../searchV2/testdata/dashboard-create.txt | 21 ++ .../searchV2/testdata/dashboard-delete.txt | 20 ++ .../searchV2/testdata/dashboard-update.txt | 21 ++ pkg/services/searchV2/types.go | 1 - 10 files changed, 575 insertions(+), 531 deletions(-) create mode 100644 pkg/services/searchV2/testdata/basic-filter.txt create mode 100644 pkg/services/searchV2/testdata/basic-search.txt create mode 100644 pkg/services/searchV2/testdata/dashboard-create.txt create mode 100644 pkg/services/searchV2/testdata/dashboard-delete.txt create mode 100644 pkg/services/searchV2/testdata/dashboard-update.txt diff --git a/pkg/services/searchV2/bluge.go b/pkg/services/searchV2/bluge.go index 19543ac31f9..0672623780a 100644 --- a/pkg/services/searchV2/bluge.go +++ b/pkg/services/searchV2/bluge.go @@ -30,145 +30,212 @@ const ( documentFieldInternalID = "__internal_id" // only for migrations! (indexed as a string) ) -func initBlugeIndex(dashboards []dashboard, logger log.Logger) (*bluge.Reader, error) { - config := bluge.InMemoryOnlyConfig() - - // open an index writer using the configuration - writer, err := bluge.OpenWriter(config) +func initIndex(dashboards []dashboard, logger log.Logger) (*bluge.Reader, *bluge.Writer, error) { + writer, err := bluge.OpenWriter(bluge.InMemoryOnlyConfig()) if err != nil { - return nil, fmt.Errorf("error opening writer: %v", err) + return nil, nil, fmt.Errorf("error opening writer: %v", err) } - defer func() { - err = writer.Close() - if err != nil { - logger.Error("Error closing bluge writer", "error", err) - } - }() - - start := time.Now() - - logger.Info("Loading dashboards for bluge index", "elapsed", time.Since(start), "numDashboards", len(dashboards)) - label := time.Now() + // Not closing Writer here since we use it later while processing dashboard change events. batch := bluge.NewBatch() - // First index the folders + start := time.Now() + label := start + + // First index the folders to construct folderIdLookup. folderIdLookup := make(map[int64]string, 50) - for _, dashboard := range dashboards { - if !dashboard.isFolder { + for _, dash := range dashboards { + if !dash.isFolder { continue } - uid := dashboard.uid - url := fmt.Sprintf("/dashboards/f/%s/%s", dashboard.uid, dashboard.slug) + doc := getFolderDashboardDoc(dash) + batch.Insert(doc) + uid := dash.uid if uid == "" { uid = "general" - url = "/dashboards" - dashboard.info.Title = "General" - dashboard.info.Description = "" - - // ARRRG, why is this not in the final index?!! } - - doc := bluge.NewDocument(uid). - AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindFolder)).Aggregatable().StoreValue()). - AddField(bluge.NewKeywordField(documentFieldURL, url).StoreValue()). - AddField(bluge.NewTextField(documentFieldName, dashboard.info.Title).StoreValue().SearchTermPositions()). - AddField(bluge.NewTextField(documentFieldDescription, dashboard.info.Description).SearchTermPositions()) - - batch.Insert(doc) - - folderIdLookup[dashboard.id] = uid + folderIdLookup[dash.id] = uid } - // Then each dashboard - for _, dashboard := range dashboards { - if dashboard.isFolder { + // Then each dashboard. + for _, dash := range dashboards { + if dash.isFolder { continue } - - url := fmt.Sprintf("/d/%s/%s", dashboard.uid, dashboard.slug) - folderUID := folderIdLookup[dashboard.folderID] + folderUID := folderIdLookup[dash.folderID] location := folderUID - - // Dashboard document - doc := bluge.NewDocument(dashboard.uid). - AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindDashboard)).Aggregatable().StoreValue()). - AddField(bluge.NewKeywordField(documentFieldURL, url).StoreValue()). - AddField(bluge.NewKeywordField(documentFieldLocation, location).Aggregatable().StoreValue()). - AddField(bluge.NewTextField(documentFieldName, dashboard.info.Title).StoreValue().SearchTermPositions()). - AddField(bluge.NewTextField(documentFieldDescription, dashboard.info.Description).SearchTermPositions()) - - // Add legacy ID (for lookup by internal ID) - doc.AddField(bluge.NewKeywordField(documentFieldInternalID, fmt.Sprintf("%d", dashboard.id))) - - for _, tag := range dashboard.info.Tags { - doc.AddField(bluge.NewKeywordField(documentFieldTag, tag). - StoreValue(). - Aggregatable(). - SearchTermPositions()) - } - - for _, ds := range dashboard.info.Datasource { - if ds.UID != "" { - doc.AddField(bluge.NewKeywordField(documentFieldDSUID, ds.UID). - StoreValue(). - Aggregatable(). - SearchTermPositions()) - } - if ds.Type != "" { - doc.AddField(bluge.NewKeywordField(documentFieldDSType, ds.Type). - StoreValue(). - Aggregatable(). - SearchTermPositions()) - } - } - - // TODO: enterprise, add dashboard sorting fields - + doc := getNonFolderDashboardDoc(dash, location) batch.Insert(doc) - location += "/" + dashboard.uid - - // Now add a doc for each panel - for _, panel := range dashboard.info.Panels { - uid := dashboard.uid + "#" + strconv.FormatInt(panel.ID, 10) - purl := url - if panel.Type != "row" { - purl = fmt.Sprintf("%s?viewPanel=%d", url, panel.ID) - } - - doc := bluge.NewDocument(uid). - AddField(bluge.NewKeywordField(documentFieldURL, purl).StoreValue()). - AddField(bluge.NewTextField(documentFieldName, panel.Title).StoreValue().SearchTermPositions()). - AddField(bluge.NewTextField(documentFieldDescription, panel.Description).SearchTermPositions()). - AddField(bluge.NewKeywordField(documentFieldPanelType, panel.Type).Aggregatable().StoreValue()). - AddField(bluge.NewKeywordField(documentFieldLocation, location).Aggregatable().StoreValue()). - AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindPanel)).Aggregatable().StoreValue()) // likely want independent index for this - - batch.Insert(doc) + // Index each panel in dashboard. + location += "/" + dash.uid + docs := getDashboardPanelDocs(dash, location) + for _, panelDoc := range docs { + batch.Insert(panelDoc) } } - - logger.Info("Inserting documents into bluge batch", "elapsed", time.Since(label)) + logger.Info("Finish inserting docs into batch", "elapsed", time.Since(label)) label = time.Now() err = writer.Batch(batch) if err != nil { - return nil, err + return nil, nil, err } + logger.Info("Finish writing batch", "elapsed", time.Since(label)) reader, err := writer.Reader() if err != nil { - return nil, err + return nil, nil, err } - logger.Info("Inserting batch into bluge writer", "elapsed", time.Since(label)) - logger.Info("Finish building bluge index", "totalElapsed", time.Since(start)) - return reader, err + logger.Info("Finish building index", "totalElapsed", time.Since(start)) + return reader, writer, err +} + +func getFolderDashboardDoc(dash dashboard) *bluge.Document { + uid := dash.uid + url := fmt.Sprintf("/dashboards/f/%s/%s", dash.uid, dash.slug) + if uid == "" { + uid = "general" + url = "/dashboards" + dash.info.Title = "General" + dash.info.Description = "" + } + + return bluge.NewDocument(uid). + AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindFolder)).Aggregatable().StoreValue()). + AddField(bluge.NewKeywordField(documentFieldURL, url).StoreValue()). + AddField(bluge.NewTextField(documentFieldName, dash.info.Title).StoreValue().SearchTermPositions()). + AddField(bluge.NewTextField(documentFieldDescription, dash.info.Description).SearchTermPositions()). + // Add legacy ID (for lookup by internal ID) + AddField(bluge.NewKeywordField(documentFieldInternalID, fmt.Sprintf("%d", dash.id)).Aggregatable().StoreValue()) +} + +func getNonFolderDashboardDoc(dash dashboard, location string) *bluge.Document { + url := fmt.Sprintf("/d/%s/%s", dash.uid, dash.slug) + + // Dashboard document + doc := bluge.NewDocument(dash.uid). + AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindDashboard)).Aggregatable().StoreValue()). + AddField(bluge.NewKeywordField(documentFieldURL, url).StoreValue()). + AddField(bluge.NewKeywordField(documentFieldLocation, location).Aggregatable().StoreValue()). + AddField(bluge.NewTextField(documentFieldName, dash.info.Title).StoreValue().SearchTermPositions()). + AddField(bluge.NewTextField(documentFieldDescription, dash.info.Description).SearchTermPositions()) + + // Add legacy ID (for lookup by internal ID) + doc.AddField(bluge.NewKeywordField(documentFieldInternalID, fmt.Sprintf("%d", dash.id))) + + for _, tag := range dash.info.Tags { + doc.AddField(bluge.NewKeywordField(documentFieldTag, tag). + StoreValue(). + Aggregatable(). + SearchTermPositions()) + } + + for _, ds := range dash.info.Datasource { + if ds.UID != "" { + doc.AddField(bluge.NewKeywordField(documentFieldDSUID, ds.UID). + StoreValue(). + Aggregatable(). + SearchTermPositions()) + } + if ds.Type != "" { + doc.AddField(bluge.NewKeywordField(documentFieldDSType, ds.Type). + StoreValue(). + Aggregatable(). + SearchTermPositions()) + } + } + + // TODO: enterprise, add dashboard sorting fields + + return doc +} + +func getDashboardPanelDocs(dash dashboard, location string) []*bluge.Document { + var docs []*bluge.Document + url := fmt.Sprintf("/d/%s/%s", dash.uid, dash.slug) + for _, panel := range dash.info.Panels { + uid := dash.uid + "#" + strconv.FormatInt(panel.ID, 10) + purl := url + if panel.Type != "row" { + purl = fmt.Sprintf("%s?viewPanel=%d", url, panel.ID) + } + + doc := bluge.NewDocument(uid). + AddField(bluge.NewKeywordField(documentFieldURL, purl).StoreValue()). + AddField(bluge.NewKeywordField(documentFieldDSUID, dash.uid).StoreValue()). + AddField(bluge.NewTextField(documentFieldName, panel.Title).StoreValue().SearchTermPositions()). + AddField(bluge.NewTextField(documentFieldDescription, panel.Description).SearchTermPositions()). + AddField(bluge.NewKeywordField(documentFieldPanelType, panel.Type).Aggregatable().StoreValue()). + AddField(bluge.NewKeywordField(documentFieldLocation, location).Aggregatable().StoreValue()). + AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindPanel)).Aggregatable().StoreValue()) // likely want independent index for this + + docs = append(docs, doc) + } + return docs +} + +func getDashboardFolderUID(reader *bluge.Reader, folderID int64) (string, error) { + fullQuery := bluge.NewBooleanQuery() + fullQuery.AddMust(bluge.NewTermQuery(strconv.FormatInt(folderID, 10)).SetField(documentFieldInternalID)) + fullQuery.AddMust(bluge.NewTermQuery(string(entityKindFolder)).SetField(documentFieldKind)) + req := bluge.NewAllMatches(fullQuery) + req.WithStandardAggregations() + documentMatchIterator, err := reader.Search(context.Background(), req) + if err != nil { + return "", err + } + var uid string + match, err := documentMatchIterator.Next() + for err == nil && match != nil { + // load the identifier for this match + err = match.VisitStoredFields(func(field string, value []byte) bool { + if field == documentFieldUID { + uid = string(value) + } + return true + }) + if err != nil { + return "", err + } + // load the next document match + match, err = documentMatchIterator.Next() + } + return uid, err +} + +func getDashboardPanelIDs(reader *bluge.Reader, dashboardUID string) ([]string, error) { + var panelIDs []string + fullQuery := bluge.NewBooleanQuery() + fullQuery.AddMust(bluge.NewTermQuery(dashboardUID).SetField(documentFieldDSUID)) + fullQuery.AddMust(bluge.NewTermQuery(string(entityKindPanel)).SetField(documentFieldKind)) + req := bluge.NewAllMatches(fullQuery) + req.WithStandardAggregations() + documentMatchIterator, err := reader.Search(context.Background(), req) + if err != nil { + return nil, err + } + match, err := documentMatchIterator.Next() + for err == nil && match != nil { + // load the identifier for this match + err = match.VisitStoredFields(func(field string, value []byte) bool { + if field == documentFieldUID { + panelIDs = append(panelIDs, string(value)) + } + return true + }) + if err != nil { + return nil, err + } + // load the next document match + match, err = documentMatchIterator.Next() + } + return panelIDs, err } //nolint: gocyclo -func doBlugeQuery(ctx context.Context, s *StandardSearchService, reader *bluge.Reader, filter ResourceFilter, q DashboardQuery) *backend.DataResponse { +func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader, filter ResourceFilter, q DashboardQuery) *backend.DataResponse { response := &backend.DataResponse{} // Folder listing structure @@ -194,7 +261,7 @@ func doBlugeQuery(ctx context.Context, s *StandardSearchService, reader *bluge.R hasConstraints := false fullQuery := bluge.NewBooleanQuery() - fullQuery.AddMust(newPermissionFilter(filter, s.logger)) + fullQuery.AddMust(newPermissionFilter(filter, logger)) // Only show dashboard / folders if len(q.Kind) > 0 { @@ -208,19 +275,12 @@ func doBlugeQuery(ctx context.Context, s *StandardSearchService, reader *bluge.R // Explicit UID lookup (stars etc) if len(q.UIDs) > 0 { + count := len(q.UIDs) + 3 bq := bluge.NewBooleanQuery() - for _, v := range q.UIDs { - bq.AddShould(bluge.NewTermQuery(v).SetField(documentFieldUID)) - } - fullQuery.AddMust(bq) - hasConstraints = true - } - - // Legacy lookup by internal ID - if len(q.IDs) > 0 { - bq := bluge.NewBooleanQuery() - for _, v := range q.IDs { - bq.AddShould(bluge.NewTermQuery(fmt.Sprintf("%d", v)).SetField(documentFieldInternalID)) + for i, v := range q.UIDs { + bq.AddShould(bluge.NewTermQuery(v). + SetField(documentFieldUID). + SetBoost(float64(count - i))) } fullQuery.AddMust(bq) hasConstraints = true @@ -293,7 +353,7 @@ func doBlugeQuery(ctx context.Context, s *StandardSearchService, reader *bluge.R // execute this search on the reader documentMatchIterator, err := reader.Search(ctx, req) if err != nil { - s.logger.Error("error executing search: %v", err) + logger.Error("error executing search: %v", err) response.Error = err return response } @@ -330,9 +390,9 @@ func doBlugeQuery(ctx context.Context, s *StandardSearchService, reader *bluge.R fTags.Name = "tags" fExplain.Name = "explain" - frame := data.NewFrame("Query results", fScore, fKind, fUID, fName, fPType, fURL, fTags, fDSUIDs, fLocation) + frame := data.NewFrame("Query results", fKind, fUID, fName, fPType, fURL, fTags, fDSUIDs, fLocation) if q.Explain { - frame.Fields = append(frame.Fields, fExplain) + frame.Fields = append(frame.Fields, fScore, fExplain) } locationItems := make(map[string]bool, 50) @@ -385,12 +445,11 @@ func doBlugeQuery(ctx context.Context, s *StandardSearchService, reader *bluge.R return true }) if err != nil { - s.logger.Error("error loading stored fields: %v", err) + logger.Error("error loading stored fields: %v", err) response.Error = err return response } - fScore.Append(match.Score) fKind.Append(kind) fUID.Append(uid) fPType.Append(ptype) @@ -422,6 +481,7 @@ func doBlugeQuery(ctx context.Context, s *StandardSearchService, reader *bluge.R } if q.Explain { + fScore.Append(match.Score) if match.Explanation != nil { js, _ := json.Marshal(&match.Explanation) jsb := json.RawMessage(js) @@ -439,9 +499,12 @@ func doBlugeQuery(ctx context.Context, s *StandardSearchService, reader *bluge.R aggs := documentMatchIterator.Aggregations() header := &customMeta{ - Count: aggs.Count(), // Total cound - MaxScore: aggs.Metric("max_score"), + Count: aggs.Count(), // Total cound } + if q.Explain { + header.MaxScore = aggs.Metric("max_score") + } + if len(locationItems) > 0 && !q.SkipLocation { header.Locations = getLocationLookupInfo(ctx, reader, locationItems) } diff --git a/pkg/services/searchV2/index.go b/pkg/services/searchV2/index.go index c6ddeecc07b..5382cc943bd 100644 --- a/pkg/services/searchV2/index.go +++ b/pkg/services/searchV2/index.go @@ -31,15 +31,6 @@ type eventStore interface { GetAllEventsAfter(ctx context.Context, id int64) ([]*store.EntityEvent, error) } -type dashboardIndex struct { - mu sync.RWMutex - loader dashboardLoader - dashboards map[int64][]dashboard // orgId -> []dashboards - reader map[int64]*bluge.Reader // orgId -> bluge index - eventStore eventStore - logger log.Logger -} - type dashboard struct { id int64 uid string @@ -51,13 +42,24 @@ type dashboard struct { info *extract.DashboardInfo } +type dashboardIndex struct { + mu sync.RWMutex + loader dashboardLoader + perOrgReader map[int64]*bluge.Reader // orgId -> bluge reader + perOrgWriter map[int64]*bluge.Writer // orgId -> bluge writer + eventStore eventStore + logger log.Logger + buildSignals chan int64 +} + func newDashboardIndex(dashLoader dashboardLoader, evStore eventStore) *dashboardIndex { return &dashboardIndex{ - loader: dashLoader, - eventStore: evStore, - dashboards: map[int64][]dashboard{}, - reader: map[int64]*bluge.Reader{}, - logger: log.New("dashboardIndex"), + loader: dashLoader, + eventStore: evStore, + perOrgReader: map[int64]*bluge.Reader{}, + perOrgWriter: map[int64]*bluge.Writer{}, + logger: log.New("dashboardIndex"), + buildSignals: make(chan int64), } } @@ -79,19 +81,26 @@ func (i *dashboardIndex) run(ctx context.Context) error { // Build on start for orgID 1 but keep lazy for others. started := time.Now() - dashboards, err := i.getDashboards(ctx, 1) + numDashboards, err := i.buildOrgIndex(ctx, 1) if err != nil { return fmt.Errorf("can't build dashboard search index for org ID 1: %w", err) } - i.logger.Info("Indexing for main org finished", "mainOrgIndexElapsed", time.Since(started), "numDashboards", len(dashboards)) - - // build bluge index on startup (will catch panics) - go i.reIndexFromScratchBluge(ctx) + i.logger.Info("Indexing for main org finished", "mainOrgIndexElapsed", time.Since(started), "numDashboards", numDashboards) for { select { case <-partialUpdateTicker.C: lastEventID = i.applyIndexUpdates(ctx, lastEventID) + case orgID := <-i.buildSignals: + i.mu.RLock() + _, ok := i.perOrgWriter[orgID] + if ok { + // Index for org already exists, do nothing. + i.mu.RUnlock() + continue + } + i.mu.RUnlock() + _, _ = i.buildOrgIndex(ctx, orgID) case <-fullReIndexTicker.C: started := time.Now() i.reIndexFromScratch(ctx) @@ -102,82 +111,67 @@ func (i *dashboardIndex) run(ctx context.Context) error { } } -func (i *dashboardIndex) reIndexFromScratch(ctx context.Context) { - i.mu.RLock() - orgIDs := make([]int64, 0, len(i.dashboards)) - for orgID := range i.dashboards { - orgIDs = append(orgIDs, orgID) - } - i.mu.RUnlock() +func (i *dashboardIndex) buildOrgIndex(ctx context.Context, orgID int64) (int, error) { + started := time.Now() + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() - for _, orgID := range orgIDs { - started := time.Now() - ctx, cancel := context.WithTimeout(ctx, time.Minute) - dashboards, err := i.loader.LoadDashboards(ctx, orgID, "") - if err != nil { - cancel() - i.logger.Error("Error re-indexing dashboards for organization", "orgId", orgID, "error", err) - continue - } - cancel() - i.logger.Info("Re-indexed dashboards for organization", "orgId", orgID, "orgReIndexElapsed", time.Since(started)) - i.mu.Lock() - i.dashboards[orgID] = dashboards - i.mu.Unlock() + i.logger.Info("Start building org index", "orgId", orgID) + dashboards, err := i.loader.LoadDashboards(ctx, orgID, "") + if err != nil { + return 0, fmt.Errorf("error loading dashboards: %w", err) } + orgSearchIndexLoadTime := time.Since(started) + i.logger.Info("Finish loading org dashboards", "elapsed", orgSearchIndexLoadTime, "orgId", orgID) + + reader, writer, err := initIndex(dashboards, i.logger) + if err != nil { + return 0, fmt.Errorf("error initializing index: %w", err) + } + orgSearchIndexTotalTime := time.Since(started) + orgSearchIndexBuildTime := orgSearchIndexTotalTime - orgSearchIndexLoadTime + + i.logger.Info("Re-indexed dashboards for organization", + "orgId", orgID, + "orgSearchIndexLoadTime", orgSearchIndexLoadTime, + "orgSearchIndexBuildTime", orgSearchIndexBuildTime, + "orgSearchIndexTotalTime", orgSearchIndexTotalTime) + + i.mu.Lock() + i.perOrgReader[orgID] = reader + i.perOrgWriter[orgID] = writer + i.mu.Unlock() + return len(dashboards), nil } -// Variation of the above function that builds bluge index from scratch -// Once the frontend is wired up, we should switch to this one -func (i *dashboardIndex) reIndexFromScratchBluge(ctx context.Context) { - // Catch Panic (just in case) - defer func() { - recv := recover() - if recv != nil { - i.logger.Error("panic in search runner", "recv", recv) // REMVOE after we are sure it works! - } - }() - +func (i *dashboardIndex) getOrgReader(orgID int64) (*bluge.Reader, bool) { i.mu.RLock() - orgIDs := make([]int64, 0, len(i.dashboards)) - for orgID := range i.dashboards { + defer i.mu.RUnlock() + r, ok := i.perOrgReader[orgID] + return r, ok +} + +func (i *dashboardIndex) getOrgWriter(orgID int64) (*bluge.Writer, bool) { + i.mu.RLock() + defer i.mu.RUnlock() + w, ok := i.perOrgWriter[orgID] + return w, ok +} + +func (i *dashboardIndex) reIndexFromScratch(ctx context.Context) { + i.mu.RLock() + orgIDs := make([]int64, 0, len(i.perOrgWriter)) + for orgID := range i.perOrgWriter { orgIDs = append(orgIDs, orgID) } i.mu.RUnlock() - if len(orgIDs) < 1 { - orgIDs = append(orgIDs, int64(1)) // make sure we index - } for _, orgID := range orgIDs { - started := time.Now() - ctx, cancel := context.WithTimeout(ctx, time.Minute) - - dashboards, err := i.loader.LoadDashboards(ctx, orgID, "") + _, err := i.buildOrgIndex(ctx, orgID) if err != nil { - cancel() i.logger.Error("Error re-indexing dashboards for organization", "orgId", orgID, "error", err) continue } - orgSearchIndexLoadTime := time.Since(started) - - reader, err := initBlugeIndex(dashboards, i.logger) - if err != nil { - cancel() - i.logger.Error("Error re-indexing dashboards for organization", "orgId", orgID, "error", err) - continue - } - orgSearchIndexTotalTime := time.Since(started) - orgSearchIndexBuildTime := orgSearchIndexTotalTime - orgSearchIndexLoadTime - - cancel() - i.logger.Info("Re-indexed dashboards for organization (bluge)", - "orgId", orgID, - "orgSearchIndexLoadTime", orgSearchIndexLoadTime, - "orgSearchIndexBuildTime", orgSearchIndexBuildTime, - "orgSearchIndexTotalTime", orgSearchIndexTotalTime) - i.mu.Lock() - i.reader[orgID] = reader - i.mu.Unlock() } } @@ -231,7 +225,7 @@ func (i *dashboardIndex) applyEventOnIndex(ctx context.Context, e *store.EntityE func (i *dashboardIndex) applyDashboardEvent(ctx context.Context, orgID int64, dashboardUID string, _ store.EntityEventType) error { i.mu.Lock() - _, ok := i.dashboards[orgID] + _, ok := i.perOrgWriter[orgID] if !ok { // Skip event for org not yet indexed. i.mu.Unlock() @@ -247,64 +241,112 @@ func (i *dashboardIndex) applyDashboardEvent(ctx context.Context, orgID int64, d i.mu.Lock() defer i.mu.Unlock() - dashboards, ok := i.dashboards[orgID] + writer, ok := i.perOrgWriter[orgID] + if !ok { + // Skip event for org not yet fully indexed. + return nil + } + reader, ok := i.perOrgReader[orgID] if !ok { // Skip event for org not yet fully indexed. return nil } + var newReader *bluge.Reader + // In the future we can rely on operation types to reduce work here. if len(dbDashboards) == 0 { - // Delete. - i.dashboards[orgID] = removeDashboard(dashboards, dashboardUID) + newReader, err = i.removeDashboard(writer, reader, dashboardUID) } else { - updated := false - for i, d := range dashboards { - if d.uid == dashboardUID { - // Update. - dashboards[i] = dbDashboards[0] - updated = true - break - } - } - if !updated { - // Create. - dashboards = append(dashboards, dbDashboards...) - } - i.dashboards[orgID] = dashboards + newReader, err = i.updateDashboard(writer, reader, dbDashboards[0]) } + if err != nil { + return err + } + i.perOrgReader[orgID] = newReader return nil } -func removeDashboard(dashboards []dashboard, dashboardUID string) []dashboard { - k := 0 - for _, d := range dashboards { - if d.uid != dashboardUID { - dashboards[k] = d - k++ - } +func (i *dashboardIndex) removeDashboard(writer *bluge.Writer, reader *bluge.Reader, dashboardUID string) (*bluge.Reader, error) { + // Find all panel docs to remove with dashboard. + panelIDs, err := getDashboardPanelIDs(reader, dashboardUID) + if err != nil { + return nil, err } - return dashboards[:k] + + batch := bluge.NewBatch() + batch.Delete(bluge.NewDocument(dashboardUID).ID()) + for _, panelID := range panelIDs { + batch.Delete(bluge.NewDocument(panelID).ID()) + } + + err = writer.Batch(batch) + if err != nil { + return nil, err + } + + return writer.Reader() } -func (i *dashboardIndex) getDashboards(ctx context.Context, orgId int64) ([]dashboard, error) { - var dashboards []dashboard +func stringInSlice(str string, slice []string) bool { + for _, s := range slice { + if s == str { + return true + } + } + return false +} - i.mu.Lock() - defer i.mu.Unlock() +func (i *dashboardIndex) updateDashboard(writer *bluge.Writer, reader *bluge.Reader, dash dashboard) (*bluge.Reader, error) { + batch := bluge.NewBatch() - if cachedDashboards, ok := i.dashboards[orgId]; ok { - dashboards = cachedDashboards + var doc *bluge.Document + if dash.isFolder { + doc = getFolderDashboardDoc(dash) } else { - // Load and parse all dashboards for given orgId. - var err error - dashboards, err = i.loader.LoadDashboards(ctx, orgId, "") + var folderUID string + if dash.folderID == 0 { + folderUID = "general" + } else { + var err error + folderUID, err = getDashboardFolderUID(reader, dash.folderID) + if err != nil { + return nil, err + } + } + + location := folderUID + doc = getNonFolderDashboardDoc(dash, location) + + var actualPanelIDs []string + + location += "/" + dash.uid + panelDocs := getDashboardPanelDocs(dash, location) + for _, panelDoc := range panelDocs { + actualPanelIDs = append(actualPanelIDs, string(panelDoc.ID().Term())) + batch.Update(panelDoc.ID(), panelDoc) + } + + indexedPanelIDs, err := getDashboardPanelIDs(reader, dash.uid) if err != nil { return nil, err } - i.dashboards[orgId] = dashboards + + for _, panelID := range indexedPanelIDs { + if !stringInSlice(panelID, actualPanelIDs) { + batch.Delete(bluge.NewDocument(panelID).ID()) + } + } } - return dashboards, nil + + batch.Update(doc.ID(), doc) + + err := writer.Batch(batch) + if err != nil { + return nil, err + } + + return writer.Reader() } type sqlDashboardLoader struct { diff --git a/pkg/services/searchV2/index_test.go b/pkg/services/searchV2/index_test.go index addad470c0a..0a8e88872df 100644 --- a/pkg/services/searchV2/index_test.go +++ b/pkg/services/searchV2/index_test.go @@ -2,9 +2,16 @@ package searchV2 import ( "context" + "flag" + "path/filepath" "testing" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/searchV2/extract" "github.com/grafana/grafana/pkg/services/store" + + "github.com/blugelabs/bluge" + "github.com/grafana/grafana-plugin-sdk-go/experimental" "github.com/stretchr/testify/require" ) @@ -12,83 +19,123 @@ type testDashboardLoader struct { dashboards []dashboard } -func (t *testDashboardLoader) LoadDashboards(ctx context.Context, orgID int64, dashboardUID string) ([]dashboard, error) { +func (t *testDashboardLoader) LoadDashboards(_ context.Context, _ int64, _ string) ([]dashboard, error) { return t.dashboards, nil } -func TestDashboardIndexCreate(t *testing.T) { +var testLogger = log.New("index-test-logger") + +var testAllowAllFilter = func(uid string) bool { + return true +} + +var testDisallowAllFilter = func(uid string) bool { + return false +} + +var update = flag.Bool("update", false, "update golden files") + +func initTestIndexFromDashes(t *testing.T, dashboards []dashboard) (*dashboardIndex, *bluge.Reader, *bluge.Writer) { + t.Helper() dashboardLoader := &testDashboardLoader{ - dashboards: []dashboard{ - { - uid: "1", - }, - }, + dashboards: dashboards, } index := newDashboardIndex(dashboardLoader, &store.MockEntityEventsService{}) require.NotNil(t, index) - dashboards, err := index.getDashboards(context.Background(), 1) + numDashboards, err := index.buildOrgIndex(context.Background(), 1) require.NoError(t, err) - require.Len(t, dashboards, 1) + require.Equal(t, len(dashboardLoader.dashboards), numDashboards) + reader, ok := index.getOrgReader(1) + require.True(t, ok) + writer, ok := index.getOrgWriter(1) + require.True(t, ok) + return index, reader, writer +} - dashboardLoader.dashboards = []dashboard{ - { +func checkSearchResponse(t *testing.T, fileName string, reader *bluge.Reader, filter ResourceFilter, query DashboardQuery) { + t.Helper() + resp := doSearchQuery(context.Background(), testLogger, reader, filter, query) + goldenFile := filepath.Join("testdata", fileName) + err := experimental.CheckGoldenDataResponse(goldenFile, resp, *update) + require.NoError(t, err) +} + +var testDashboards = []dashboard{ + { + id: 1, + uid: "1", + info: &extract.DashboardInfo{ + Title: "test", + }, + }, + { + id: 2, + uid: "2", + info: &extract.DashboardInfo{ + Title: "boom", + }, + }, +} + +func TestDashboardIndex(t *testing.T) { + t.Run("basic-search", func(t *testing.T) { + _, reader, _ := initTestIndexFromDashes(t, testDashboards) + checkSearchResponse(t, filepath.Base(t.Name())+".txt", reader, testAllowAllFilter, + DashboardQuery{Query: "boom"}, + ) + }) + + t.Run("basic-filter", func(t *testing.T) { + _, reader, _ := initTestIndexFromDashes(t, testDashboards) + checkSearchResponse(t, filepath.Base(t.Name())+".txt", reader, testDisallowAllFilter, + DashboardQuery{Query: "boom"}, + ) + }) +} + +func TestDashboardIndexUpdates(t *testing.T) { + t.Run("dashboard-delete", func(t *testing.T) { + index, reader, writer := initTestIndexFromDashes(t, testDashboards) + + newReader, err := index.removeDashboard(writer, reader, "2") + require.NoError(t, err) + + checkSearchResponse(t, filepath.Base(t.Name())+".txt", newReader, testAllowAllFilter, + DashboardQuery{Query: "boom"}, + ) + }) + + t.Run("dashboard-create", func(t *testing.T) { + index, reader, writer := initTestIndexFromDashes(t, testDashboards) + + newReader, err := index.updateDashboard(writer, reader, dashboard{ + id: 3, + uid: "3", + info: &extract.DashboardInfo{ + Title: "created", + }, + }) + require.NoError(t, err) + + checkSearchResponse(t, filepath.Base(t.Name())+".txt", newReader, testAllowAllFilter, + DashboardQuery{Query: "created"}, + ) + }) + + t.Run("dashboard-update", func(t *testing.T) { + index, reader, writer := initTestIndexFromDashes(t, testDashboards) + + newReader, err := index.updateDashboard(writer, reader, dashboard{ + id: 2, uid: "2", - }, - } - err = index.applyDashboardEvent(context.Background(), 1, "2", "") - require.NoError(t, err) - dashboards, err = index.getDashboards(context.Background(), 1) - require.NoError(t, err) - require.Len(t, dashboards, 2) -} - -func TestDashboardIndexUpdate(t *testing.T) { - dashboardLoader := &testDashboardLoader{ - dashboards: []dashboard{ - { - uid: "1", - slug: "test", + info: &extract.DashboardInfo{ + Title: "nginx", }, - }, - } - index := newDashboardIndex(dashboardLoader, nil) - require.NotNil(t, index) - dashboards, err := index.getDashboards(context.Background(), 1) - require.NoError(t, err) - require.Len(t, dashboards, 1) + }) + require.NoError(t, err) - dashboardLoader.dashboards = []dashboard{ - { - uid: "1", - slug: "updated", - }, - } - err = index.applyDashboardEvent(context.Background(), 1, "1", "") - require.NoError(t, err) - dashboards, err = index.getDashboards(context.Background(), 1) - require.NoError(t, err) - require.Len(t, dashboards, 1) - require.Equal(t, "updated", dashboards[0].slug) -} - -func TestDashboardIndexDelete(t *testing.T) { - dashboardLoader := &testDashboardLoader{ - dashboards: []dashboard{ - { - uid: "1", - }, - }, - } - index := newDashboardIndex(dashboardLoader, nil) - require.NotNil(t, index) - dashboards, err := index.getDashboards(context.Background(), 1) - require.NoError(t, err) - require.Len(t, dashboards, 1) - - dashboardLoader.dashboards = []dashboard{} - err = index.applyDashboardEvent(context.Background(), 1, "1", "") - require.NoError(t, err) - dashboards, err = index.getDashboards(context.Background(), 1) - require.NoError(t, err) - require.Len(t, dashboards, 0) + checkSearchResponse(t, filepath.Base(t.Name())+".txt", newReader, testAllowAllFilter, + DashboardQuery{Query: "nginx"}, + ) + }) } diff --git a/pkg/services/searchV2/service.go b/pkg/services/searchV2/service.go index 773318d45bb..a3b17ba1811 100644 --- a/pkg/services/searchV2/service.go +++ b/pkg/services/searchV2/service.go @@ -2,23 +2,18 @@ package searchV2 import ( "context" - "encoding/json" "errors" - "fmt" - "strconv" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/searchV2/extract" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/data" ) type StandardSearchService struct { @@ -105,9 +100,9 @@ func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backen return user, nil } -func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *backend.User, orgId int64, q DashboardQuery) *backend.DataResponse { +func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *backend.User, orgID int64, q DashboardQuery) *backend.DataResponse { rsp := &backend.DataResponse{} - signedInUser, err := s.getUser(ctx, user, orgId) + signedInUser, err := s.getUser(ctx, user, orgID) if err != nil { rsp.Error = err return rsp @@ -119,219 +114,14 @@ func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *back return rsp } - reader := s.dashboardIndex.reader[orgId] - if reader != nil && q.Query != "" { // frontend initializes with empty string - return doBlugeQuery(ctx, s, reader, filter, q) - } - - dashboards, err := s.dashboardIndex.getDashboards(ctx, orgId) - if err != nil { - rsp.Error = err + reader, ok := s.dashboardIndex.getOrgReader(orgID) + if !ok { + go func() { + s.dashboardIndex.buildSignals <- orgID + }() + rsp.Error = errors.New("search index is not ready, try again later") return rsp } - dashboards = s.applyAuthFilter(filter, dashboards) - - rsp.Frames = metaToFrame(dashboards) - - return rsp -} - -func (s *StandardSearchService) applyAuthFilter(filter ResourceFilter, dashboards []dashboard) []dashboard { - // create a list of all viewable dashboards for this user. - res := make([]dashboard, 0, len(dashboards)) - for _, dash := range dashboards { - if filter(dash.uid) || (dash.isFolder && dash.uid == "") { // include the "General" folder - res = append(res, dash) - } - } - return res -} - -type simpleCounter struct { - values map[string]int64 -} - -func (c *simpleCounter) add(key string) { - v, ok := c.values[key] - if !ok { - v = 0 - } - c.values[key] = v + 1 -} - -func (c *simpleCounter) toFrame(name string) *data.Frame { - key := data.NewFieldFromFieldType(data.FieldTypeString, 0) - val := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - for k, v := range c.values { - key.Append(k) - val.Append(v) - } - return data.NewFrame(name, key, val) -} - -// UGLY... but helpful for now -func metaToFrame(meta []dashboard) data.Frames { - folderID := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - folderUID := data.NewFieldFromFieldType(data.FieldTypeString, 0) - folderName := data.NewFieldFromFieldType(data.FieldTypeString, 0) - folderDashCount := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - - folderID.Name = "id" - folderUID.Name = "uid" - folderName.Name = "name" - folderDashCount.Name = "DashCount" - - dashID := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - dashUID := data.NewFieldFromFieldType(data.FieldTypeString, 0) - dashURL := data.NewFieldFromFieldType(data.FieldTypeString, 0) - dashFolderID := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - dashName := data.NewFieldFromFieldType(data.FieldTypeString, 0) - dashDescr := data.NewFieldFromFieldType(data.FieldTypeString, 0) - dashCreated := data.NewFieldFromFieldType(data.FieldTypeTime, 0) - dashUpdated := data.NewFieldFromFieldType(data.FieldTypeTime, 0) - dashSchemaVersion := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - dashTags := data.NewFieldFromFieldType(data.FieldTypeNullableString, 0) - dashPanelCount := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - dashVarCount := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - dashDSList := data.NewFieldFromFieldType(data.FieldTypeNullableString, 0) - - dashID.Name = "id" - dashUID.Name = "uid" - dashFolderID.Name = "folderID" - dashName.Name = "name" - dashDescr.Name = "description" - dashTags.Name = "tags" - dashSchemaVersion.Name = "SchemaVersion" - dashCreated.Name = "Created" - dashUpdated.Name = "Updated" - dashURL.Name = "url" - dashURL.Config = &data.FieldConfig{ - Links: []data.DataLink{ - {Title: "link", URL: "${__value.text}"}, - }, - } - dashPanelCount.Name = "panelCount" - dashVarCount.Name = "varCount" - dashDSList.Name = "datasource" - - dashTags.Config = &data.FieldConfig{ - Custom: map[string]interface{}{ - // Table panel default styling - "displayMode": "json-view", - }, - } - - panelDashID := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - panelID := data.NewFieldFromFieldType(data.FieldTypeInt64, 0) - panelName := data.NewFieldFromFieldType(data.FieldTypeString, 0) - panelDescr := data.NewFieldFromFieldType(data.FieldTypeString, 0) - panelType := data.NewFieldFromFieldType(data.FieldTypeString, 0) - - panelDashID.Name = "dashboardID" - panelID.Name = "id" - panelName.Name = "name" - panelDescr.Name = "description" - panelType.Name = "type" - - panelTypeCounter := simpleCounter{ - values: make(map[string]int64, 30), - } - - schemaVersionCounter := simpleCounter{ - values: make(map[string]int64, 30), - } - - folderCounter := make(map[int64]int64, 20) - - for _, row := range meta { - if row.isFolder { - folderID.Append(row.id) - folderUID.Append(row.uid) - folderName.Append(row.info.Title) - folderDashCount.Append(int64(0)) // filled in later - continue - } - - dashID.Append(row.id) - dashUID.Append(row.uid) - dashFolderID.Append(row.folderID) - dashName.Append(row.info.Title) - dashDescr.Append(row.info.Title) - dashSchemaVersion.Append(row.info.SchemaVersion) - dashCreated.Append(row.created) - dashUpdated.Append(row.updated) - - // Increment the folder counter - fcount, ok := folderCounter[row.folderID] - if !ok { - fcount = 0 - } - folderCounter[row.folderID] = fcount + 1 - - url := fmt.Sprintf("/d/%s/%s", row.uid, row.slug) - dashURL.Append(url) - - // stats - schemaVersionCounter.add(strconv.FormatInt(row.info.SchemaVersion, 10)) - - dashTags.Append(toJSONString(row.info.Tags)) - dashPanelCount.Append(int64(len(row.info.Panels))) - dashVarCount.Append(int64(len(row.info.TemplateVars))) - dashDSList.Append(dsAsJSONString(row.info.Datasource)) - - // Row for each panel - for _, panel := range row.info.Panels { - panelDashID.Append(row.id) - panelID.Append(panel.ID) - panelName.Append(panel.Title) - panelDescr.Append(panel.Description) - panelType.Append(panel.Type) - panelTypeCounter.add(panel.Type) - } - } - - // Update the folder counts - for i := 0; i < folderID.Len(); i++ { - id, ok := folderID.At(i).(int64) - if ok { - folderDashCount.Set(i, folderCounter[id]) - } - } - - return data.Frames{ - data.NewFrame("folders", folderID, folderUID, folderName, folderDashCount), - data.NewFrame("dashboards", dashID, dashUID, dashURL, dashFolderID, - dashName, dashDescr, dashTags, - dashSchemaVersion, - dashPanelCount, dashVarCount, dashDSList, - dashCreated, dashUpdated), - data.NewFrame("panels", panelDashID, panelID, panelName, panelDescr, panelType), - panelTypeCounter.toFrame("panel-type-counts"), - schemaVersionCounter.toFrame("schema-version-counts"), - } -} - -func toJSONString(vals []string) *string { - if len(vals) < 1 { - return nil - } - b, err := json.Marshal(vals) - if err == nil { - s := string(b) - return &s - } - return nil -} - -func dsAsJSONString(vals []extract.DataSourceRef) *string { - if len(vals) < 1 { - return nil - } - b, err := json.Marshal(vals) - if err == nil { - s := string(b) - return &s - } - return nil + return doSearchQuery(ctx, s.logger, reader, filter, q) } diff --git a/pkg/services/searchV2/testdata/basic-filter.txt b/pkg/services/searchV2/testdata/basic-filter.txt new file mode 100644 index 00000000000..904ff864265 --- /dev/null +++ b/pkg/services/searchV2/testdata/basic-filter.txt @@ -0,0 +1,20 @@ +🌟 This was machine generated. Do not edit. 🌟 + +Frame[0] { + "type": "search-results", + "custom": { + "count": 0 + } +} +Name: Query results +Dimensions: 8 Fields by 0 Rows ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +| Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +| 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 | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ + + +====== TEST DATA RESPONSE (arrow base64) ====== +FRAME=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cmwiOiIke19fdmFsdWUudGV4dH0ifV19AAAAAAYAAABjb25maWcAAAAAAACI/v//AwAAAHVybAD2/v//FAAAAEAAAABAAAAAAAAABTwAAAABAAAABAAAAOT+//8IAAAAFAAAAAoAAABwYW5lbF90eXBlAAAEAAAAbmFtZQAAAAAAAAAA4P7//woAAABwYW5lbF90eXBlAABW////FAAAADwAAAA8AAAAAAAABTgAAAABAAAABAAAAET///8IAAAAEAAAAAQAAABuYW1lAAAAAAQAAABuYW1lAAAAAAAAAAA8////BAAAAG5hbWUAAAAArv///xQAAAA4AAAAOAAAAAAAAAU0AAAAAQAAAAQAAACc////CAAAAAwAAAADAAAAdWlkAAQAAABuYW1lAAAAAAAAAACQ////AwAAAHVpZAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAARAAAAEgAAAAAAAAFRAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAABAAAAAEAAAAa2luZAAAAAAEAAAAbmFtZQAAAAAAAAAABAAEAAQAAAAEAAAAa2luZAAAAABgBAAAQVJST1cx diff --git a/pkg/services/searchV2/testdata/basic-search.txt b/pkg/services/searchV2/testdata/basic-search.txt new file mode 100644 index 00000000000..89375780cb3 --- /dev/null +++ b/pkg/services/searchV2/testdata/basic-search.txt @@ -0,0 +1,21 @@ +🌟 This was machine generated. Do not edit. 🌟 + +Frame[0] { + "type": "search-results", + "custom": { + "count": 1 + } +} +Name: Query results +Dimensions: 8 Fields by 1 Rows ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +| Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +| 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 | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +| dashboard | 2 | boom | | /d/2/ | null | null | | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ + + +====== TEST DATA RESPONSE (arrow base64) ====== +FRAME=QVJST1cxAAD/////UAQAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAAKwAAAADAAAAWAAAACgAAAAEAAAAPPz//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAABc/P//CAAAABgAAAANAAAAUXVlcnkgcmVzdWx0cwAAAAQAAABuYW1lAAAAAIj8//8IAAAAOAAAAC4AAAB7InR5cGUiOiJzZWFyY2gtcmVzdWx0cyIsImN1c3RvbSI6eyJjb3VudCI6MX19AAAEAAAAbWV0YQAAAAAIAAAACAMAAKACAABEAgAA4AEAADQBAADYAAAAaAAAAAQAAAAq/f//FAAAAEAAAABAAAAAAAAABTwAAAABAAAABAAAABj9//8IAAAAFAAAAAgAAABsb2NhdGlvbgAAAAAEAAAAbmFtZQAAAAAAAAAAFP3//wgAAABsb2NhdGlvbgAAAACm////FAAAADwAAAA8AAAAAAAEATgAAAABAAAABAAAAHj9//8IAAAAEAAAAAYAAABkc191aWQAAAQAAABuYW1lAAAAAAAAAABw/f//BgAAAGRzX3VpZAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAADwAAAA8AAAAAAAEATgAAAABAAAABAAAAOT9//8IAAAAEAAAAAQAAAB0YWdzAAAAAAQAAABuYW1lAAAAAAAAAADc/f//BAAAAHRhZ3MAAAAATv7//xQAAACQAAAAkAAAAAAAAAWMAAAAAgAAACgAAAAEAAAAQP7//wgAAAAMAAAAAwAAAHVybAAEAAAAbmFtZQAAAABg/v//CAAAAEAAAAA0AAAAeyJsaW5rcyI6W3sidGl0bGUiOiJsaW5rIiwidXJsIjoiJHtfX3ZhbHVlLnRleHR9In1dfQAAAAAGAAAAY29uZmlnAAAAAAAAiP7//wMAAAB1cmwA9v7//xQAAABAAAAAQAAAAAAAAAU8AAAAAQAAAAQAAADk/v//CAAAABQAAAAKAAAAcGFuZWxfdHlwZQAABAAAAG5hbWUAAAAAAAAAAOD+//8KAAAAcGFuZWxfdHlwZQAAVv///xQAAAA8AAAAPAAAAAAAAAU4AAAAAQAAAAQAAABE////CAAAABAAAAAEAAAAbmFtZQAAAAAEAAAAbmFtZQAAAAAAAAAAPP///wQAAABuYW1lAAAAAK7///8UAAAAOAAAADgAAAAAAAAFNAAAAAEAAAAEAAAAnP///wgAAAAMAAAAAwAAAHVpZAAEAAAAbmFtZQAAAAAAAAAAkP///wMAAAB1aWQAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAEQAAABIAAAAAAAABUQAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAQAAAABAAAAGtpbmQAAAAABAAAAG5hbWUAAAAAAAAAAAQABAAEAAAABAAAAGtpbmQAAAAAAAAAAP////9YAgAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAeAAAAAAAAAAUAAAAAAAAAwQACgAYAAwACAAEAAoAAAAUAAAAmAEAAAEAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAACQAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAIAAAAAAAAACAAAAAAAAAAAQAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAIAAAAAAAAADAAAAAAAAAABAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAIAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAIAAAAAAAAAEgAAAAAAAAABQAAAAAAAABQAAAAAAAAAAEAAAAAAAAAWAAAAAAAAAAIAAAAAAAAAGAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAEAAAAAAAAAaAAAAAAAAAAIAAAAAAAAAHAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAIAAAAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAkAAABkYXNoYm9hcmQAAAAAAAAAAAAAAAEAAAAyAAAAAAAAAAAAAAAEAAAAYm9vbQAAAAAAAAAAAAAAAAAAAAAFAAAAL2QvMi8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAwAFAASAAwACAAEAAwAAAAQAAAALAAAADgAAAAAAAQAAQAAAGAEAAAAAAAAYAIAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAKAAwAAAAIAAQACgAAAAgAAACsAAAAAwAAAFgAAAAoAAAABAAAADz8//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAAXPz//wgAAAAYAAAADQAAAFF1ZXJ5IHJlc3VsdHMAAAAEAAAAbmFtZQAAAACI/P//CAAAADgAAAAuAAAAeyJ0eXBlIjoic2VhcmNoLXJlc3VsdHMiLCJjdXN0b20iOnsiY291bnQiOjF9fQAABAAAAG1ldGEAAAAACAAAAAgDAACgAgAARAIAAOABAAA0AQAA2AAAAGgAAAAEAAAAKv3//xQAAABAAAAAQAAAAAAAAAU8AAAAAQAAAAQAAAAY/f//CAAAABQAAAAIAAAAbG9jYXRpb24AAAAABAAAAG5hbWUAAAAAAAAAABT9//8IAAAAbG9jYXRpb24AAAAApv///xQAAAA8AAAAPAAAAAAABAE4AAAAAQAAAAQAAAB4/f//CAAAABAAAAAGAAAAZHNfdWlkAAAEAAAAbmFtZQAAAAAAAAAAcP3//wYAAABkc191aWQAAAAAEgAYABQAEwASAAwAAAAIAAQAEgAAABQAAAA8AAAAPAAAAAAABAE4AAAAAQAAAAQAAADk/f//CAAAABAAAAAEAAAAdGFncwAAAAAEAAAAbmFtZQAAAAAAAAAA3P3//wQAAAB0YWdzAAAAAE7+//8UAAAAkAAAAJAAAAAAAAAFjAAAAAIAAAAoAAAABAAAAED+//8IAAAADAAAAAMAAAB1cmwABAAAAG5hbWUAAAAAYP7//wgAAABAAAAANAAAAHsibGlua3MiOlt7InRpdGxlIjoibGluayIsInVybCI6IiR7X192YWx1ZS50ZXh0fSJ9XX0AAAAABgAAAGNvbmZpZwAAAAAAAIj+//8DAAAAdXJsAPb+//8UAAAAQAAAAEAAAAAAAAAFPAAAAAEAAAAEAAAA5P7//wgAAAAUAAAACgAAAHBhbmVsX3R5cGUAAAQAAABuYW1lAAAAAAAAAADg/v//CgAAAHBhbmVsX3R5cGUAAFb///8UAAAAPAAAADwAAAAAAAAFOAAAAAEAAAAEAAAARP///wgAAAAQAAAABAAAAG5hbWUAAAAABAAAAG5hbWUAAAAAAAAAADz///8EAAAAbmFtZQAAAACu////FAAAADgAAAA4AAAAAAAABTQAAAABAAAABAAAAJz///8IAAAADAAAAAMAAAB1aWQABAAAAG5hbWUAAAAAAAAAAJD///8DAAAAdWlkAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAASAAAAAAAAAVEAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABraW5kAAAAAAQAAABuYW1lAAAAAAAAAAAEAAQABAAAAAQAAABraW5kAAAAAHgEAABBUlJPVzE= diff --git a/pkg/services/searchV2/testdata/dashboard-create.txt b/pkg/services/searchV2/testdata/dashboard-create.txt new file mode 100644 index 00000000000..79b98ad2fb2 --- /dev/null +++ b/pkg/services/searchV2/testdata/dashboard-create.txt @@ -0,0 +1,21 @@ +🌟 This was machine generated. Do not edit. 🌟 + +Frame[0] { + "type": "search-results", + "custom": { + "count": 1 + } +} +Name: Query results +Dimensions: 8 Fields by 1 Rows ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +| Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +| 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 | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +| dashboard | 3 | created | | /d/3/ | null | null | general | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ + + +====== TEST DATA RESPONSE (arrow base64) ====== +FRAME=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 diff --git a/pkg/services/searchV2/testdata/dashboard-delete.txt b/pkg/services/searchV2/testdata/dashboard-delete.txt new file mode 100644 index 00000000000..904ff864265 --- /dev/null +++ b/pkg/services/searchV2/testdata/dashboard-delete.txt @@ -0,0 +1,20 @@ +🌟 This was machine generated. Do not edit. 🌟 + +Frame[0] { + "type": "search-results", + "custom": { + "count": 0 + } +} +Name: Query results +Dimensions: 8 Fields by 0 Rows ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +| Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +| 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 | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ + + +====== TEST DATA RESPONSE (arrow base64) ====== +FRAME=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cmwiOiIke19fdmFsdWUudGV4dH0ifV19AAAAAAYAAABjb25maWcAAAAAAACI/v//AwAAAHVybAD2/v//FAAAAEAAAABAAAAAAAAABTwAAAABAAAABAAAAOT+//8IAAAAFAAAAAoAAABwYW5lbF90eXBlAAAEAAAAbmFtZQAAAAAAAAAA4P7//woAAABwYW5lbF90eXBlAABW////FAAAADwAAAA8AAAAAAAABTgAAAABAAAABAAAAET///8IAAAAEAAAAAQAAABuYW1lAAAAAAQAAABuYW1lAAAAAAAAAAA8////BAAAAG5hbWUAAAAArv///xQAAAA4AAAAOAAAAAAAAAU0AAAAAQAAAAQAAACc////CAAAAAwAAAADAAAAdWlkAAQAAABuYW1lAAAAAAAAAACQ////AwAAAHVpZAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAARAAAAEgAAAAAAAAFRAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAABAAAAAEAAAAa2luZAAAAAAEAAAAbmFtZQAAAAAAAAAABAAEAAQAAAAEAAAAa2luZAAAAABgBAAAQVJST1cx diff --git a/pkg/services/searchV2/testdata/dashboard-update.txt b/pkg/services/searchV2/testdata/dashboard-update.txt new file mode 100644 index 00000000000..5ff092ec67a --- /dev/null +++ b/pkg/services/searchV2/testdata/dashboard-update.txt @@ -0,0 +1,21 @@ +🌟 This was machine generated. Do not edit. 🌟 + +Frame[0] { + "type": "search-results", + "custom": { + "count": 1 + } +} +Name: Query results +Dimensions: 8 Fields by 1 Rows ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +| Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | +| 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 | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ +| dashboard | 2 | nginx | | /d/2/ | null | null | general | ++----------------+----------------+----------------+------------------+----------------+--------------------------+--------------------------+----------------+ + + +====== 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 33475bd7398..adb64f21eca 100644 --- a/pkg/services/searchV2/types.go +++ b/pkg/services/searchV2/types.go @@ -21,7 +21,6 @@ type DashboardQuery struct { Tags []string `json:"tags,omitempty"` Kind []string `json:"kind,omitempty"` UIDs []string `json:"uid,omitempty"` - IDs []int64 `json:"id,omitempty"` // deprecated -- but will convert internal ID to UIDs Explain bool `json:"explain,omitempty"` // adds details on why document matched Facet []FacetField `json:"facet,omitempty"` SkipLocation bool `json:"skipLocation,omitempty"`