Dashboard: Improve search error response (#113617)
* Dashboard: Improve search error response * improve errors
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
@@ -62,7 +63,7 @@ func ParseSortName(sortName string) (string, bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return "", false, fmt.Errorf("no matching sort field found for: %s", sortName)
|
||||
return "", false, apierrors.NewBadRequest(fmt.Sprintf("no matching sort field found for: %s", sortName))
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
@@ -97,11 +98,11 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
|
||||
case folders.RESOURCE:
|
||||
queryType = searchstore.TypeFolder
|
||||
default:
|
||||
return nil, fmt.Errorf("bad type request")
|
||||
return nil, apierrors.NewBadRequest("bad type request")
|
||||
}
|
||||
|
||||
if len(req.Federated) > 1 {
|
||||
return nil, fmt.Errorf("bad type request")
|
||||
return nil, apierrors.NewBadRequest("bad type request")
|
||||
}
|
||||
|
||||
if len(req.Federated) == 1 &&
|
||||
@@ -117,7 +118,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
|
||||
sortByField := ""
|
||||
if len(req.SortBy) != 0 {
|
||||
if len(req.SortBy) > 1 {
|
||||
return nil, fmt.Errorf("only one sort field is supported")
|
||||
return nil, apierrors.NewBadRequest("only one sort field is supported")
|
||||
}
|
||||
sort := req.SortBy[0]
|
||||
sortByField = strings.TrimPrefix(sort.Field, resource.SEARCH_FIELD_PREFIX)
|
||||
@@ -208,13 +209,13 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
|
||||
case resource.SEARCH_FIELD_SOURCE_PATH:
|
||||
// only one value is supported in legacy search
|
||||
if len(vals) != 1 {
|
||||
return nil, fmt.Errorf("only one repo path query is supported")
|
||||
return nil, apierrors.NewBadRequest("only one repo path query is supported")
|
||||
}
|
||||
query.SourcePath = vals[0]
|
||||
|
||||
case resource.SEARCH_FIELD_MANAGER_KIND:
|
||||
if len(vals) != 1 {
|
||||
return nil, fmt.Errorf("only one manager kind supported")
|
||||
return nil, apierrors.NewBadRequest("only one manager kind supported")
|
||||
}
|
||||
query.ManagedBy = utils.ManagerKind(vals[0])
|
||||
|
||||
@@ -226,20 +227,20 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
|
||||
|
||||
// only one value is supported in legacy search
|
||||
if len(vals) != 1 {
|
||||
return nil, fmt.Errorf("only one repo name is supported")
|
||||
return nil, apierrors.NewBadRequest("only one repo name is supported")
|
||||
}
|
||||
query.ManagerIdentity = vals[0]
|
||||
|
||||
case unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE:
|
||||
if len(vals) != 1 {
|
||||
return nil, fmt.Errorf("only one library panel uid is supported")
|
||||
return nil, apierrors.NewBadRequest("only one library panel uid is supported")
|
||||
}
|
||||
|
||||
// Make sure the query does not include incompatible combinations
|
||||
for _, f := range req.Options.Fields {
|
||||
switch f.Key {
|
||||
case resource.SEARCH_FIELD_NAME:
|
||||
return nil, fmt.Errorf("libraryPanel query must not include explicit names")
|
||||
return nil, apierrors.NewBadRequest("libraryPanel query must not include explicit names")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +258,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
|
||||
|
||||
case resource.SEARCH_FIELD_TITLE_PHRASE:
|
||||
if len(vals) != 1 {
|
||||
return nil, fmt.Errorf("only one title supported")
|
||||
return nil, apierrors.NewBadRequest("only one title supported")
|
||||
}
|
||||
|
||||
query.Title = vals[0]
|
||||
@@ -276,7 +277,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
|
||||
// legacy sql query, since legacy search does not support this
|
||||
if query.ManagerIdentity != "" || len(query.ManagerIdentityNotIn) > 0 || query.ManagedBy != "" {
|
||||
if query.ManagedBy == utils.ManagerKindUnknown {
|
||||
return nil, fmt.Errorf("query by manager identity also requires manager.kind parameter")
|
||||
return nil, apierrors.NewBadRequest("query by manager identity also requires manager.kind parameter")
|
||||
}
|
||||
|
||||
// for plugin and orphaned dashboards, we will only return the manager kind alongside the regular search response
|
||||
@@ -399,19 +400,19 @@ func (c *DashboardSearchClient) getLibraryPanelConnections(ctx context.Context,
|
||||
func (c *DashboardSearchClient) GetStats(ctx context.Context, req *resourcepb.ResourceStatsRequest, _ ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) {
|
||||
info, err := claims.ParseNamespace(req.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read namespace")
|
||||
return nil, apierrors.NewInternalError(fmt.Errorf("unable to read namespace: %w", err))
|
||||
}
|
||||
if info.OrgID == 0 {
|
||||
return nil, fmt.Errorf("invalid OrgID found in namespace")
|
||||
return nil, apierrors.NewInternalError(fmt.Errorf("invalid OrgID found in namespace"))
|
||||
}
|
||||
|
||||
if len(req.Kinds) != 1 {
|
||||
return nil, fmt.Errorf("only can query for dashboard kind in legacy fallback")
|
||||
return nil, apierrors.NewBadRequest("only can query for dashboard kind in legacy fallback")
|
||||
}
|
||||
|
||||
parts := strings.SplitN(req.Kinds[0], "/", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid kind")
|
||||
return nil, apierrors.NewBadRequest("invalid kind")
|
||||
}
|
||||
|
||||
var count int64
|
||||
@@ -421,7 +422,7 @@ func (c *DashboardSearchClient) GetStats(ctx context.Context, req *resourcepb.Re
|
||||
case folders.GROUP:
|
||||
count, err = c.dashboardStore.CountInOrg(ctx, info.OrgID, true)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid group")
|
||||
return nil, apierrors.NewBadRequest("invalid group")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -645,6 +645,54 @@ func TestDashboardSearchClient_Search(t *testing.T) {
|
||||
require.Contains(t, err.Error(), "only one library panel uid is supported")
|
||||
require.Nil(t, resp)
|
||||
})
|
||||
|
||||
t.Run("Should reject library panel query when combined with explicit dashboard names", func(t *testing.T) {
|
||||
req := &resourcepb.ResourceSearchRequest{
|
||||
Options: &resourcepb.ListOptions{
|
||||
Key: dashboardKey,
|
||||
Fields: []*resourcepb.Requirement{
|
||||
{
|
||||
Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE,
|
||||
Operator: "=",
|
||||
Values: []string{"test-library-panel"},
|
||||
},
|
||||
{
|
||||
Key: resource.SEARCH_FIELD_NAME,
|
||||
Operator: "=",
|
||||
Values: []string{"dashboard-uid"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "libraryPanel query must not include explicit names")
|
||||
require.Nil(t, resp)
|
||||
// Note: mockStore should NOT be called since validation happens before any store calls
|
||||
})
|
||||
|
||||
t.Run("Should return empty results when library panel has no connected dashboards", func(t *testing.T) {
|
||||
mockStore.On("GetDashboardsByLibraryPanelUID", mock.Anything, "unused-library-panel", int64(2)).Return([]*dashboards.DashboardRef{}, nil).Once()
|
||||
|
||||
req := &resourcepb.ResourceSearchRequest{
|
||||
Options: &resourcepb.ListOptions{
|
||||
Key: dashboardKey,
|
||||
Fields: []*resourcepb.Requirement{
|
||||
{
|
||||
Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE,
|
||||
Operator: "=",
|
||||
Values: []string{"unused-library-panel"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Search(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
require.Equal(t, int64(0), resp.TotalHits)
|
||||
require.Empty(t, resp.Results.Rows)
|
||||
mockStore.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseSortName(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user