From 5c0194955e054329a5468af553d288a5e09d47a3 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Sat, 24 May 2025 08:06:30 -0500 Subject: [PATCH] Dashboards: Fix FindDashboards when kubernetesClientDashboardsFolders is disabled (#105974) Dashboards: fix legacy FindDashboards --- .../dashboard/legacysearcher/search_client.go | 35 +--------- .../legacysearcher/search_client_test.go | 8 +-- pkg/services/dashboards/database/database.go | 34 ++++++++- .../dashboards/database/database_test.go | 62 ++++++++--------- .../dashboards/service/dashboard_service.go | 69 ++++++++----------- 5 files changed, 95 insertions(+), 113 deletions(-) diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client.go b/pkg/registry/apis/dashboard/legacysearcher/search_client.go index 014ee911db6..667d208cc06 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client.go @@ -304,9 +304,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso return nil, err } - hits := formatQueryResult(res) - - for _, dashboard := range hits { + for _, dashboard := range res { tags, err := json.Marshal(dashboard.Tags) if err != nil { return nil, err @@ -324,7 +322,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso } list.Results.Rows = append(list.Results.Rows, &resourcepb.ResourceTableRow{ - Key: getResourceKey(dashboard, req.Options.Key.Namespace), + Key: getResourceKey(&dashboard, req.Options.Key.Namespace), Cells: cells, }) } @@ -352,35 +350,6 @@ func getResourceKey(item *dashboards.DashboardSearchProjection, namespace string } } -func formatQueryResult(res []dashboards.DashboardSearchProjection) []*dashboards.DashboardSearchProjection { - hitList := make([]*dashboards.DashboardSearchProjection, 0) - hits := make(map[string]*dashboards.DashboardSearchProjection) - - for _, item := range res { - key := fmt.Sprintf("%s-%d", item.UID, item.OrgID) - hit, exists := hits[key] - if !exists { - hit = &dashboards.DashboardSearchProjection{ - ID: item.ID, - UID: item.UID, - Title: item.Title, - FolderUID: item.FolderUID, - Tags: []string{}, - IsFolder: item.IsFolder, - SortMeta: item.SortMeta, - } - hitList = append(hitList, hit) - hits[key] = hit - } - - if len(item.Term) > 0 { - hit.Tags = append(hit.Tags, item.Term) - } - } - - return hitList -} - func (c *DashboardSearchClient) GetStats(ctx context.Context, req *resourcepb.ResourceStatsRequest, _ ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) { info, err := claims.ParseNamespace(req.Namespace) if err != nil { diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go index 9161e16a68e..eeb0a18c469 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go @@ -44,8 +44,8 @@ func TestDashboardSearchClient_Search(t *testing.T) { Type: "dash-db", // should set type based off of key Sort: sorter, }).Return([]dashboards.DashboardSearchProjection{ - {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"}, - {ID: 2, UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"}, + {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Tags: []string{"term"}}, + {ID: 2, UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2", Tags: []string{}}, }, nil).Once() req := &resourcepb.ResourceSearchRequest{ @@ -121,7 +121,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Type: "dash-db", Sort: sortOptionAsc, }).Return([]dashboards.DashboardSearchProjection{ - {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)}, + {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50), Tags: []string{}}, }, nil).Once() req := &resourcepb.ResourceSearchRequest{ @@ -186,7 +186,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Type: "dash-db", Sort: sortOptionAsc, }).Return([]dashboards.DashboardSearchProjection{ - {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)}, + {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2), Tags: []string{}}, }, nil).Once() req := &resourcepb.ResourceSearchRequest{ diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 24d6c0e7d07..4830b564543 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -922,7 +922,39 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F return nil, err } - return res, nil + if len(res) <= 1 { + return res, nil + } + + // the search query above will return one row per dashboard tag, and dashboards + // can have multiple tags. we only want to return one row per dashboard, so dedup + // the results by id. + // note: we must preserve the order of the results as we dedup, as the query can be sorted + seen := make(map[int64]int) + uniqueRes := make([]dashboards.DashboardSearchProjection, 0, len(res)) + + for _, item := range res { + if idx, exists := seen[item.ID]; exists { + if item.Term != "" { + if uniqueRes[idx].Tags == nil { + uniqueRes[idx].Tags = make([]string, 0) + } + uniqueRes[idx].Tags = append(uniqueRes[idx].Tags, item.Term) + } + continue + } + + if item.Tags == nil { + item.Tags = make([]string, 0) + } + if item.Term != "" { + item.Tags = append(item.Tags, item.Term) + } + seen[item.ID] = len(uniqueRes) + uniqueRes = append(uniqueRes, item) + } + + return uniqueRes, nil } func (d *dashboardStore) GetDashboardTags(ctx context.Context, query *dashboards.GetDashboardTagsQuery) ([]*dashboards.DashboardTagCloudItem, error) { diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index 91fa92392e8..ff148d1e0a2 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -841,7 +841,7 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) { require.NoError(t, err) orgID := int64(1) - insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false) + insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false, []string{"tag1", "tag2"}) ac := acimpl.ProvideAccessControl(features) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) @@ -869,7 +869,7 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) { }) require.NoError(t, err) - insertTestDashboard(t, dashboardStore, "dashboard under f0", orgID, 0, f0.UID, false) + insertTestDashboard(t, dashboardStore, "dashboard under f0", orgID, 0, f0.UID, false, []string{"tag3"}) subfolder, err := folderServiceWithFlagOn.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, @@ -1244,43 +1244,35 @@ func testSearchDashboards(d dashboards.Store, query *dashboards.FindPersistedDas func makeQueryResult(query *dashboards.FindPersistedDashboardsQuery, res []dashboards.DashboardSearchProjection) model.HitList { hitList := make([]*model.Hit, 0) - hits := make(map[int64]*model.Hit) for _, item := range res { - hit, exists := hits[item.ID] - if !exists { - hitType := model.DashHitDB - if item.IsFolder { - hitType = model.DashHitFolder - } - - hit = &model.Hit{ - ID: item.ID, - UID: item.UID, - Title: item.Title, - URI: "db/" + item.Slug, - URL: dashboards.GetDashboardFolderURL(item.IsFolder, item.UID, item.Slug), - Type: hitType, - FolderUID: item.FolderUID, - FolderTitle: item.FolderTitle, - Tags: []string{}, - } - - if item.FolderUID != "" { - hit.FolderURL = dashboards.GetFolderURL(item.FolderUID, item.FolderSlug) - } - - if query.Sort.MetaName != "" { - hit.SortMeta = item.SortMeta - hit.SortMetaName = query.Sort.MetaName - } - - hitList = append(hitList, hit) - hits[item.ID] = hit + hitType := model.DashHitDB + if item.IsFolder { + hitType = model.DashHitFolder } - if len(item.Term) > 0 { - hit.Tags = append(hit.Tags, item.Term) + + hit := &model.Hit{ + ID: item.ID, + UID: item.UID, + Title: item.Title, + URI: "db/" + item.Slug, + URL: dashboards.GetDashboardFolderURL(item.IsFolder, item.UID, item.Slug), + Type: hitType, + FolderUID: item.FolderUID, + FolderTitle: item.FolderTitle, + Tags: item.Tags, } + + if item.FolderUID != "" { + hit.FolderURL = dashboards.GetFolderURL(item.FolderUID, item.FolderSlug) + } + + if query.Sort.MetaName != "" { + hit.SortMeta = item.SortMeta + hit.SortMetaName = query.Sort.MetaName + } + + hitList = append(hitList, hit) } return hitList } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 430b3174ef0..c5333e6c0bc 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -1650,55 +1650,44 @@ func getHitType(item dashboards.DashboardSearchProjection) model.HitType { func makeQueryResult(query *dashboards.FindPersistedDashboardsQuery, res []dashboards.DashboardSearchProjection) model.HitList { hitList := make([]*model.Hit, 0) - hits := make(map[string]*model.Hit) for _, item := range res { - key := fmt.Sprintf("%s-%d", item.UID, item.OrgID) - hit, exists := hits[key] - if !exists { - metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc() - hit = &model.Hit{ - ID: item.ID, - UID: item.UID, - OrgID: item.OrgID, - Title: item.Title, - URI: "db/" + item.Slug, - URL: dashboards.GetDashboardFolderURL(item.IsFolder, item.UID, item.Slug), - Type: getHitType(item), - FolderID: item.FolderID, // nolint:staticcheck - FolderUID: item.FolderUID, - FolderTitle: item.FolderTitle, - Tags: []string{}, - } - - // when searching through unified storage, the dashboard will come as one - // item, when searching through legacy, the dashboard will come multiple times - // per tag. So we need to add the array here for unified, and the term below for legacy. - if item.Tags != nil { - hit.Tags = item.Tags - } - - // nolint:staticcheck - if item.FolderID > 0 || item.FolderUID != "" { - hit.FolderURL = dashboards.GetFolderURL(item.FolderUID, item.FolderSlug) - } - - if query.Sort.MetaName != "" { - hit.SortMeta = item.SortMeta - hit.SortMetaName = query.Sort.MetaName - } - - hitList = append(hitList, hit) - hits[key] = hit + metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc() + hit := &model.Hit{ + ID: item.ID, + UID: item.UID, + OrgID: item.OrgID, + Title: item.Title, + URI: "db/" + item.Slug, + URL: dashboards.GetDashboardFolderURL(item.IsFolder, item.UID, item.Slug), + Type: getHitType(item), + FolderID: item.FolderID, // nolint:staticcheck + FolderUID: item.FolderUID, + FolderTitle: item.FolderTitle, + Tags: []string{}, } - if len(item.Term) > 0 { - hit.Tags = append(hit.Tags, item.Term) + + if item.Tags != nil { + hit.Tags = item.Tags } + + // nolint:staticcheck + if item.FolderID > 0 || item.FolderUID != "" { + hit.FolderURL = dashboards.GetFolderURL(item.FolderUID, item.FolderSlug) + } + + if query.Sort.MetaName != "" { + hit.SortMeta = item.SortMeta + hit.SortMetaName = query.Sort.MetaName + } + if item.Deleted != nil { deletedDate := (*item.Deleted).Add(daysInTrash) hit.IsDeleted = true hit.PermanentlyDeleteDate = &deletedDate } + + hitList = append(hitList, hit) } return hitList }