[search] Legacy search fallback support legacy query params (#99765)
* add support for deleted query param * support tag query param in modes 2 and below * handle dashboardIds * hhandle dashboardUIDs * handle folderUIDs query param * handle page query param when hitting legacy storage * handle sort query param * handle type query param * re-enable search fallback * remove folder search workaround and fix /api/search to return both folders and dashboards when no title or type is provided --------- Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
This commit is contained in:
co-authored by
Stephanie Hingtgen
parent
ccb0e9222a
commit
1467d4b3e3
@@ -179,10 +179,12 @@ func (h *k8sHandler) Search(ctx context.Context, orgID int64, in *resource.Resou
|
||||
in.Options = &resource.ListOptions{}
|
||||
}
|
||||
|
||||
in.Options.Key = &resource.ResourceKey{
|
||||
Namespace: h.GetNamespace(orgID),
|
||||
Group: h.gvr.Group,
|
||||
Resource: h.gvr.Resource,
|
||||
if in.Options.Key == nil {
|
||||
in.Options.Key = &resource.ResourceKey{
|
||||
Namespace: h.GetNamespace(orgID),
|
||||
Group: h.gvr.Group,
|
||||
Resource: h.gvr.Resource,
|
||||
}
|
||||
}
|
||||
|
||||
return h.searcher.Search(ctx, in)
|
||||
|
||||
@@ -27,7 +27,9 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard"
|
||||
dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
@@ -93,7 +95,7 @@ func ProvideDashboardServiceImpl(
|
||||
restConfigProvider apiserver.RestConfigProvider, userService user.Service, unified resource.ResourceClient,
|
||||
quotaService quota.Service, orgService org.Service, publicDashboardService publicdashboards.ServiceWrapper,
|
||||
) (*DashboardServiceImpl, error) {
|
||||
k8sHandler := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), v0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider, unified, dashboardStore, userService)
|
||||
k8sHandler := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), dashboardv0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider, unified, dashboardStore, userService)
|
||||
|
||||
dashSvc := &DashboardServiceImpl{
|
||||
cfg: cfg,
|
||||
@@ -1266,7 +1268,7 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb
|
||||
}
|
||||
foldersMap[hit.Folder] = f
|
||||
}
|
||||
finalResults[i] = dashboards.DashboardSearchProjection{
|
||||
result := dashboards.DashboardSearchProjection{
|
||||
ID: hit.Field.GetNestedInt64(search.DASHBOARD_LEGACY_ID),
|
||||
UID: hit.Name,
|
||||
OrgID: query.OrgId,
|
||||
@@ -1277,6 +1279,12 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb
|
||||
FolderTitle: f.Title,
|
||||
Tags: hit.Tags,
|
||||
}
|
||||
|
||||
if hit.Resource == folderv0alpha1.RESOURCE {
|
||||
result.IsFolder = true
|
||||
}
|
||||
|
||||
finalResults[i] = result
|
||||
}
|
||||
|
||||
return finalResults, nil
|
||||
@@ -1644,7 +1652,7 @@ func (dr *DashboardServiceImpl) listDashboardsThroughK8s(ctx context.Context, or
|
||||
return dashboards, nil
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) (*v0alpha1.SearchResults, error) {
|
||||
func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) (*dashboardv0alpha1.SearchResults, error) {
|
||||
request := &resource.ResourceSearchRequest{
|
||||
Options: &resource.ListOptions{
|
||||
Fields: []*resource.Requirement{},
|
||||
@@ -1722,8 +1730,55 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
|
||||
request.Options.Fields = append(request.Options.Fields, req...)
|
||||
}
|
||||
|
||||
if query.Limit > 0 {
|
||||
request.Limit = query.Limit
|
||||
if query.IsDeleted {
|
||||
request.IsDeleted = query.IsDeleted
|
||||
}
|
||||
|
||||
if query.Limit < 1 {
|
||||
query.Limit = 1000
|
||||
}
|
||||
|
||||
if query.Page < 1 {
|
||||
query.Page = 1
|
||||
}
|
||||
|
||||
request.Limit = query.Limit
|
||||
request.Page = query.Page
|
||||
request.Offset = query.Page - 1 // bleve's offset is 0 indexed
|
||||
|
||||
namespace := dr.k8sclient.GetNamespace(query.OrgId)
|
||||
var err error
|
||||
var federate *resource.ResourceKey
|
||||
switch query.Type {
|
||||
case "":
|
||||
// When no type specified, search for dashboards
|
||||
request.Options.Key, err = resource.AsResourceKey(namespace, dashboard.DASHBOARD_RESOURCE)
|
||||
// Currently a search query is across folders and dashboards
|
||||
if err == nil {
|
||||
federate, err = resource.AsResourceKey(namespace, folderv0alpha1.RESOURCE)
|
||||
}
|
||||
case searchstore.TypeDashboard, searchstore.TypeAnnotation:
|
||||
request.Options.Key, err = resource.AsResourceKey(namespace, dashboard.DASHBOARD_RESOURCE)
|
||||
case searchstore.TypeFolder, searchstore.TypeAlertFolder:
|
||||
request.Options.Key, err = resource.AsResourceKey(namespace, folderv0alpha1.RESOURCE)
|
||||
default:
|
||||
err = fmt.Errorf("bad type request")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if federate != nil {
|
||||
request.Federated = []*resource.ResourceKey{federate}
|
||||
}
|
||||
|
||||
// technically, there exists the ability to register multiple ways of sorting using the legacy database
|
||||
// see RegisterSortOption in pkg/services/search/sorting.go
|
||||
// however, it doesn't look like we are taking advantage of that. And since by default the legacy
|
||||
// sql will sort by title ascending, we only really need to handle the "alpha-desc" case
|
||||
if query.Sort.Name == "alpha-desc" {
|
||||
request.SortBy = append(request.SortBy, &resource.ResourceSearchRequest_Sort{Field: resource.SEARCH_FIELD_TITLE, Desc: true})
|
||||
}
|
||||
|
||||
res, err := dr.k8sclient.Search(ctx, query.OrgId, request)
|
||||
@@ -1770,7 +1825,7 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex
|
||||
var mu sync.Mutex
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
for _, h := range searchResults.Hits {
|
||||
func(hit v0alpha1.DashboardHit) {
|
||||
func(hit dashboardv0alpha1.DashboardHit) {
|
||||
g.Go(func() error {
|
||||
out, err := dr.k8sclient.Get(ctx, hit.Name, query.OrgId, v1.GetOptions{})
|
||||
if err != nil {
|
||||
@@ -2000,7 +2055,7 @@ func LegacySaveCommandToUnstructured(cmd *dashboards.SaveDashboardCommand, names
|
||||
finalObj.Object["spec"] = obj
|
||||
finalObj.SetName(uid)
|
||||
finalObj.SetNamespace(namespace)
|
||||
finalObj.SetGroupVersionKind(v0alpha1.DashboardResourceInfo.GroupVersionKind())
|
||||
finalObj.SetGroupVersionKind(dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind())
|
||||
|
||||
meta, err := utils.MetaAccessor(&finalObj)
|
||||
if err != nil {
|
||||
|
||||
@@ -333,6 +333,7 @@ func TestGetDashboard(t *testing.T) {
|
||||
OrgID: 1,
|
||||
}
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
@@ -538,6 +539,7 @@ func TestGetProvisionedDashboardData(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled and get from relevant org", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
@@ -636,6 +638,7 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled and get from whatever org it is in", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
@@ -725,6 +728,7 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{
|
||||
"metadata": map[string]any{
|
||||
"name": "uid",
|
||||
@@ -812,6 +816,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled, delete across all orgs, but only delete file based provisioned dashboards", func(t *testing.T) {
|
||||
_, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid", OrgID: 1}).Return(nil).Once()
|
||||
fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid3", OrgID: 2}).Return(nil).Once()
|
||||
@@ -1044,6 +1049,7 @@ func TestGetDashboardsByPluginID(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(uidUnstructured, nil)
|
||||
k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil)
|
||||
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool {
|
||||
@@ -1263,6 +1269,7 @@ func TestDeleteDashboard(t *testing.T) {
|
||||
|
||||
t.Run("If UID is not passed in, it should retrieve that first", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
@@ -1394,6 +1401,7 @@ func TestSearchDashboards(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -1513,6 +1521,7 @@ func TestGetDashboards(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Get", mock.Anything, "uid1", mock.Anything, mock.Anything, mock.Anything).Return(uid1Unstructured, nil)
|
||||
k8sCliMock.On("Get", mock.Anything, "uid2", mock.Anything, mock.Anything, mock.Anything).Return(uid2Unstructured, nil)
|
||||
k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil)
|
||||
@@ -1595,6 +1604,7 @@ func TestGetDashboardUIDByID(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -1892,6 +1902,7 @@ func TestCountInFolders(t *testing.T) {
|
||||
|
||||
t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) {
|
||||
ctx, k8sCliMock := setupK8sDashboardTests(service)
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(dashs, nil).Once()
|
||||
result, err := service.CountInFolders(ctx, 1, []string{"folder1"}, &user.SignedInUser{})
|
||||
require.NoError(t, err)
|
||||
@@ -1906,6 +1917,7 @@ func TestSearchDashboardsThroughK8sRaw(t *testing.T) {
|
||||
query := &dashboards.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
}
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
@@ -1972,6 +1984,7 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) {
|
||||
},
|
||||
"spec": map[string]any{},
|
||||
}}
|
||||
k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
|
||||
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/search/model"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/services/star"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -114,20 +113,6 @@ func (s *SearchService) SearchHandler(ctx context.Context, query *Query) (model.
|
||||
dashboardQuery.Sort = sortOpt
|
||||
}
|
||||
|
||||
// if folders are stored in unified storage, we need to use the folder service to query for folders
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) && (query.Type == searchstore.TypeFolder || query.Type == searchstore.TypeAlertFolder) {
|
||||
hits, err := s.folderService.SearchFolders(ctx, folder.SearchFoldersQuery{
|
||||
OrgID: query.OrgId,
|
||||
UIDs: query.FolderUIDs,
|
||||
IDs: query.FolderIds,
|
||||
Title: query.Title,
|
||||
Limit: query.Limit,
|
||||
SignedInUser: query.SignedInUser,
|
||||
})
|
||||
|
||||
return sortedHits(hits), err
|
||||
}
|
||||
|
||||
hits, err := s.dashboardService.SearchDashboards(ctx, &dashboardQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user