package dashboardsearch import ( "encoding/binary" "encoding/json" "fmt" "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" ) var ( // These fields exist at the top-level of DashboardHit standardFields = map[string]string{ resource.SEARCH_FIELD_EXPLAIN: "", resource.SEARCH_FIELD_SCORE: "", resource.SEARCH_FIELD_TITLE: "", resource.SEARCH_FIELD_FOLDER: "", resource.SEARCH_FIELD_TAGS: "", resource.SEARCH_FIELD_DESCRIPTION: "", resource.SEARCH_FIELD_MANAGER_ID: "", resource.SEARCH_FIELD_MANAGER_KIND: "", } IncludeFields = []string{ resource.SEARCH_FIELD_TITLE, resource.SEARCH_FIELD_TAGS, resource.SEARCH_FIELD_LABELS, resource.SEARCH_FIELD_FOLDER, resource.SEARCH_FIELD_DESCRIPTION, resource.SEARCH_FIELD_CREATED, resource.SEARCH_FIELD_CREATED_BY, resource.SEARCH_FIELD_UPDATED, resource.SEARCH_FIELD_UPDATED_BY, resource.SEARCH_FIELD_MANAGER_KIND, resource.SEARCH_FIELD_MANAGER_ID, resource.SEARCH_FIELD_SOURCE_PATH, resource.SEARCH_FIELD_SOURCE_CHECKSUM, resource.SEARCH_FIELD_SOURCE_TIME, // below is needed to determine whether a provisioned dashboard exists or not resource.SEARCH_FIELD_LEGACY_ID, resource.SEARCH_FIELD_LABELS + "." + resource.SEARCH_FIELD_LEGACY_ID, } ) // nolint:gocyclo func ParseResults(result *resourcepb.ResourceSearchResponse, offset int64) (v0alpha1.SearchResults, error) { if result == nil { return v0alpha1.SearchResults{}, nil } else if result.Error != nil { return v0alpha1.SearchResults{}, fmt.Errorf("%d error searching: %s: %s", result.Error.Code, result.Error.Message, result.Error.Details) } else if result.Results == nil { return v0alpha1.SearchResults{}, nil } titleIDX := -1 folderIDX := -1 tagsIDX := -1 descriptionIDX := -1 scoreIDX := -1 explainIDX := -1 managerKindIDX := -1 managerIdIDX := -1 for i, v := range result.Results.Columns { switch v.Name { case resource.SEARCH_FIELD_EXPLAIN: explainIDX = i case resource.SEARCH_FIELD_SCORE: scoreIDX = i case resource.SEARCH_FIELD_TITLE: titleIDX = i case resource.SEARCH_FIELD_FOLDER: folderIDX = i case resource.SEARCH_FIELD_TAGS: tagsIDX = i case resource.SEARCH_FIELD_MANAGER_ID: managerIdIDX = i case resource.SEARCH_FIELD_MANAGER_KIND: managerKindIDX = i case resource.SEARCH_FIELD_DESCRIPTION: descriptionIDX = i } } sr := v0alpha1.SearchResults{ Offset: offset, TotalHits: result.TotalHits, QueryCost: result.QueryCost, MaxScore: result.MaxScore, Hits: make([]v0alpha1.DashboardHit, len(result.Results.Rows)), } for i, row := range result.Results.Rows { if len(row.Cells) != len(result.Results.Columns) { // there should never be mismatch len between # Columns and # Cells in a row. This indicates a bug in our // code return v0alpha1.SearchResults{}, fmt.Errorf("error parsing Search Response: mismatch number of columns and cells") } // Dynamically defined fields fields := &common.Unstructured{} for colIndex, col := range result.Results.Columns { if _, ok := standardFields[col.Name]; !ok { val, err := resource.DecodeCell(col, colIndex, row.Cells[colIndex]) if err != nil { return v0alpha1.SearchResults{}, err } // Some of the dashboard fields come in as int32, but we need to convert them to int64 or else fields.Set() will panic int32Val, ok := val.(int32) if ok { val = int64(int32Val) } fields.Set(col.Name, val) } } hit := &v0alpha1.DashboardHit{ Resource: row.Key.Resource, // folders | dashboards Name: row.Key.Name, // The Grafana UID Field: fields, } if titleIDX >= 0 && row.Cells[titleIDX] != nil { hit.Title = string(row.Cells[titleIDX]) } else { hit.Title = "(no title)" } if folderIDX >= 0 && row.Cells[folderIDX] != nil { hit.Folder = string(row.Cells[folderIDX]) } if descriptionIDX >= 0 && row.Cells[descriptionIDX] != nil { hit.Description = string(row.Cells[descriptionIDX]) } if managerIdIDX >= 0 && row.Cells[managerIdIDX] != nil { hit.ManagedBy.ID = string(row.Cells[managerIdIDX]) } if managerKindIDX >= 0 && row.Cells[managerKindIDX] != nil { hit.ManagedBy.Kind = utils.ManagerKind(row.Cells[managerKindIDX]) } if tagsIDX >= 0 && row.Cells[tagsIDX] != nil { _ = json.Unmarshal(row.Cells[tagsIDX], &hit.Tags) } if explainIDX >= 0 && row.Cells[explainIDX] != nil { _ = json.Unmarshal(row.Cells[explainIDX], &hit.Explain) } if scoreIDX >= 0 && row.Cells[scoreIDX] != nil { _, _ = binary.Decode(row.Cells[scoreIDX], binary.BigEndian, &hit.Score) } sr.Hits[i] = *hit } // Add facet results if result.Facet != nil { sr.Facets = make(map[string]v0alpha1.FacetResult) for k, v := range result.Facet { sr.Facets[k] = v0alpha1.FacetResult{ Field: v.Field, Total: v.Total, Missing: v.Missing, Terms: make([]v0alpha1.TermFacet, len(v.Terms)), } for j, t := range v.Terms { sr.Facets[k].Terms[j] = v0alpha1.TermFacet{ Term: t.Term, Count: t.Count, } } } } return sr, nil }