Files

178 lines
5.3 KiB
Go

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
}