Unified Storage: Make all dashboard fields searchable (#98899)
* wip. adding sprinkles fields. * some refactoring. Works with sprinkles now. * exclude top level dashboard hit fields from hit "fields" * adds unit test for DecodeCell helper * test can search for specific dashboard fields on bleve index * adds search handler tests for the fields and tests for fields when transforming the search req to a bleve search req * fix panic when calling fields.Set() with int32 * adds regression test * remove unneeded method on test mock client * fix linter issues * updates dashboard test data for bleve tests * remove DASHBOARD_LEGACY_ID from bleve_tests * dont cast twice * updates test to sort by dashboard_views_last_1_days * declare excludedFields outside of function * fixes sorting by dashboard fields - prepends "fields." to any dashboard fields we try to sort by * uses map for excludedFields * expects fields to be array-style url param * change method name * fixes failing tests - needed to add column type to mocks
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel"
|
||||
"golang.org/x/exp/slices"
|
||||
@@ -71,6 +72,16 @@ var (
|
||||
tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/dashboards/service")
|
||||
)
|
||||
|
||||
var (
|
||||
excludedFields = map[string]string{
|
||||
resource.SEARCH_FIELD_EXPLAIN: "",
|
||||
resource.SEARCH_FIELD_SCORE: "",
|
||||
resource.SEARCH_FIELD_TITLE: "",
|
||||
resource.SEARCH_FIELD_FOLDER: "",
|
||||
resource.SEARCH_FIELD_TAGS: "",
|
||||
}
|
||||
)
|
||||
|
||||
type DashboardServiceImpl struct {
|
||||
cfg *setting.Cfg
|
||||
log log.Logger
|
||||
@@ -1949,11 +1960,11 @@ func ParseResults(result *resource.ResourceSearchResponse, offset int64) (*v0alp
|
||||
explainIDX = i
|
||||
case resource.SEARCH_FIELD_SCORE:
|
||||
scoreIDX = i
|
||||
case "title":
|
||||
case resource.SEARCH_FIELD_TITLE:
|
||||
titleIDX = i
|
||||
case "folder":
|
||||
case resource.SEARCH_FIELD_FOLDER:
|
||||
folderIDX = i
|
||||
case "tags":
|
||||
case resource.SEARCH_FIELD_TAGS:
|
||||
tagsIDX = i
|
||||
}
|
||||
}
|
||||
@@ -1967,11 +1978,28 @@ func ParseResults(result *resource.ResourceSearchResponse, offset int64) (*v0alp
|
||||
}
|
||||
|
||||
for i, row := range result.Results.Rows {
|
||||
fields := &common.Unstructured{}
|
||||
for colIndex, col := range result.Results.Columns {
|
||||
if _, ok := excludedFields[col.Name]; ok {
|
||||
val, err := resource.DecodeCell(col, colIndex, row.Cells[colIndex])
|
||||
if err != nil {
|
||||
return nil, 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
|
||||
Title: string(row.Cells[titleIDX]),
|
||||
Folder: string(row.Cells[folderIDX]),
|
||||
Field: fields,
|
||||
}
|
||||
if tagsIDX > 0 && row.Cells[tagsIDX] != nil {
|
||||
_ = json.Unmarshal(row.Cells[tagsIDX], &hit.Tags)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/unified/search"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -417,9 +418,11 @@ func TestGetDashboard(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -636,9 +639,11 @@ func TestGetProvisionedDashboardData(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -729,9 +734,11 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -810,9 +817,11 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -917,9 +926,11 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -946,9 +957,11 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -1039,9 +1052,11 @@ func TestUnprovisionDashboard(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -1095,9 +1110,11 @@ func TestGetDashboardsByPluginID(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -1303,9 +1320,11 @@ func TestDeleteDashboard(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -1422,12 +1441,15 @@ func TestSearchDashboards(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "tags",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -1521,9 +1543,11 @@ func TestGetDashboards(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -1599,9 +1623,11 @@ func TestGetDashboardUIDByID(t *testing.T) {
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
@@ -1877,3 +1903,47 @@ func TestToUID(t *testing.T) {
|
||||
assert.Equal(t, "", result)
|
||||
})
|
||||
}
|
||||
|
||||
// regression test - parsing int32 values from search results was causing a panic
|
||||
func TestParseResults(t *testing.T) {
|
||||
resSearchResp := &resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: search.DASHBOARD_ERRORS_LAST_1_DAYS,
|
||||
Type: resource.ResourceTableColumnDefinition_INT64,
|
||||
},
|
||||
{
|
||||
Name: search.DASHBOARD_LINK_COUNT,
|
||||
Type: resource.ResourceTableColumnDefinition_INT32,
|
||||
},
|
||||
},
|
||||
Rows: []*resource.ResourceTableRow{
|
||||
{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: "uid",
|
||||
Resource: "dashboard",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte("Dashboard 1"),
|
||||
[]byte("folder1"),
|
||||
[]byte("100"),
|
||||
[]byte("25"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TotalHits: 1,
|
||||
}
|
||||
|
||||
_, err := ParseResults(resSearchResp, 0)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user