From ab99211b4094073bfca42fe9299b86a6f1e610f2 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Thu, 29 May 2025 01:39:07 -0500 Subject: [PATCH] Dashboard Provisioning: Reduce db load (#106114) --- pkg/api/dashboard_test.go | 2 +- .../dashboard/legacysearcher/search_client.go | 195 ++++++--- .../legacysearcher/search_client_test.go | 8 +- pkg/services/dashboards/dashboard.go | 6 +- pkg/services/dashboards/database/database.go | 55 ++- .../dashboards/database/database_test.go | 10 +- pkg/services/dashboards/models.go | 8 + .../dashboards/service/dashboard_service.go | 140 +++---- .../service/dashboard_service_test.go | 370 ++++++++++-------- pkg/services/dashboards/store_mock.go | 30 +- pkg/storage/unified/resource/document.go | 18 + 11 files changed, 491 insertions(+), 351 deletions(-) diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 384f9ae4e94..7dad46032c4 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -710,7 +710,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { t.Run("Given provisioned dashboard", func(t *testing.T) { mockSQLStore := dbtest.NewFakeDB() dashboardStore := dashboards.NewFakeDashboardStore(t) - dashboardStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioning{ExternalID: "/dashboard1.json"}, nil).Once() + dashboardStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioningSearchResults{ExternalID: "/dashboard1.json"}, nil).Once() dashboardService := dashboards.NewFakeDashboardService(t) diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client.go b/pkg/registry/apis/dashboard/legacysearcher/search_client.go index 667d208cc06..9a9549bfc92 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client.go @@ -231,21 +231,8 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso query.ManagerIdentity = vals[0] } } - searchFields := resource.StandardSearchFields() - columns := []*resourcepb.ResourceTableColumnDefinition{ - searchFields.Field(resource.SEARCH_FIELD_TITLE), - searchFields.Field(resource.SEARCH_FIELD_FOLDER), - searchFields.Field(resource.SEARCH_FIELD_TAGS), - searchFields.Field(resource.SEARCH_FIELD_LEGACY_ID), - } - - if sortByField != "" { - columns = append(columns, &resourcepb.ResourceTableColumnDefinition{ - Name: sortByField, - Type: resourcepb.ResourceTableColumnDefinition_INT64, - }) - } + columns := c.getColumns(sortByField, query) list := &resourcepb.ResourceSearchResponse{ Results: &resourcepb.ResourceTable{ Columns: columns, @@ -254,48 +241,71 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso // if we are querying for provisioning information, we need to use a different // legacy sql query, since legacy search does not support this - if query.ManagerIdentity != "" || len(query.ManagerIdentityNotIn) > 0 { + 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") } - var dashes []*dashboards.Dashboard - if query.ManagedBy == utils.ManagerKindPlugin { - dashes, err = c.dashboardStore.GetDashboardsByPluginID(ctx, &dashboards.GetDashboardsByPluginIDQuery{ - PluginID: query.ManagerIdentity, - OrgID: user.GetOrgID(), - }) - } else if query.ManagerIdentity != "" { - dashes, err = c.dashboardStore.GetProvisionedDashboardsByName(ctx, query.ManagerIdentity, user.GetOrgID()) - } else if len(query.ManagerIdentityNotIn) > 0 { - dashes, err = c.dashboardStore.GetOrphanedProvisionedDashboards(ctx, query.ManagerIdentityNotIn, user.GetOrgID()) - } - if err != nil { - return nil, err - } - - for _, dashboard := range dashes { - cells := [][]byte{ - []byte(dashboard.Title), - []byte(dashboard.FolderUID), - []byte("[]"), // no tags retrieved for provisioned dashboards - []byte(strconv.FormatInt(dashboard.ID, 10)), + // for plugin and orphaned dashboards, we will only return the manager kind alongside the regular search response + if query.ManagedBy == utils.ManagerKindPlugin || len(query.ManagerIdentityNotIn) > 0 { + var dashes []*dashboards.Dashboard + if query.ManagedBy == utils.ManagerKindPlugin { + dashes, err = c.dashboardStore.GetDashboardsByPluginID(ctx, &dashboards.GetDashboardsByPluginIDQuery{ + PluginID: query.ManagerIdentity, + OrgID: user.GetOrgID(), + }) + } else { + dashes, err = c.dashboardStore.GetOrphanedProvisionedDashboards(ctx, query.ManagerIdentityNotIn, user.GetOrgID()) + } + if err != nil { + return nil, err } - if sortByField != "" { - cells = append(cells, []byte("0")) + for _, dashboard := range dashes { + list.Results.Rows = append(list.Results.Rows, &resourcepb.ResourceTableRow{ + Key: getResourceKey(&dashboards.DashboardSearchProjection{ + UID: dashboard.UID, + }, req.Options.Key.Namespace), + Cells: c.createProvisioningCells(dashboard, query), + }) } + list.TotalHits = int64(len(list.Results.Rows)) + return list, nil + } + + // for classic FP, we will return the regular search response alongside all the data in the dashboard_provisioning table + provisioningData := []*dashboards.DashboardProvisioningSearchResults{} + if query.ManagerIdentity == "" { + var data *dashboards.DashboardProvisioningSearchResults + if len(query.DashboardIds) > 0 { + data, err = c.dashboardStore.GetProvisionedDataByDashboardID(ctx, query.DashboardIds[0]) + } else if len(query.DashboardUIDs) > 0 { + data, err = c.dashboardStore.GetProvisionedDataByDashboardUID(ctx, user.GetOrgID(), query.DashboardUIDs[0]) + } + if err != nil { + return nil, err + } + if data != nil { + provisioningData = append(provisioningData, data) + } + } else { + provisioningData, err = c.dashboardStore.GetProvisionedDashboardsByName(ctx, query.ManagerIdentity, user.GetOrgID()) + if err != nil { + return nil, err + } + } + + for _, dashboard := range provisioningData { list.Results.Rows = append(list.Results.Rows, &resourcepb.ResourceTableRow{ Key: getResourceKey(&dashboards.DashboardSearchProjection{ - UID: dashboard.UID, + UID: dashboard.Dashboard.UID, }, req.Options.Key.Namespace), - Cells: cells, + Cells: c.createDetailedProvisioningCells(dashboard, query), }) } list.TotalHits = int64(len(list.Results.Rows)) - return list, nil } @@ -305,22 +315,11 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso } for _, dashboard := range res { - tags, err := json.Marshal(dashboard.Tags) + cells, err := c.createBaseCells(dashboard, sortByField) if err != nil { return nil, err } - cells := [][]byte{ - []byte(dashboard.Title), - []byte(dashboard.FolderUID), - tags, - []byte(strconv.FormatInt(dashboard.ID, 10)), - } - - if sortByField != "" { - cells = append(cells, []byte(strconv.FormatInt(dashboard.SortMeta, 10))) - } - list.Results.Rows = append(list.Results.Rows, &resourcepb.ResourceTableRow{ Key: getResourceKey(&dashboard, req.Options.Key.Namespace), Cells: cells, @@ -391,3 +390,93 @@ func (c *DashboardSearchClient) GetStats(ctx context.Context, req *resourcepb.Re }, }, nil } + +func (c *DashboardSearchClient) getColumns(sortByField string, query *dashboards.FindPersistedDashboardsQuery) []*resourcepb.ResourceTableColumnDefinition { + searchFields := resource.StandardSearchFields() + columns := []*resourcepb.ResourceTableColumnDefinition{ + searchFields.Field(resource.SEARCH_FIELD_TITLE), + searchFields.Field(resource.SEARCH_FIELD_FOLDER), + searchFields.Field(resource.SEARCH_FIELD_TAGS), + searchFields.Field(resource.SEARCH_FIELD_LEGACY_ID), + } + + if query.ManagerIdentity != "" || len(query.ManagerIdentityNotIn) > 0 || query.ManagedBy != "" { + columns = append(columns, &resourcepb.ResourceTableColumnDefinition{ + Name: resource.SEARCH_FIELD_MANAGER_KIND, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }) + + if query.ManagedBy != utils.ManagerKindPlugin && len(query.ManagerIdentityNotIn) == 0 { + columns = append(columns, []*resourcepb.ResourceTableColumnDefinition{ + { + Name: resource.SEARCH_FIELD_MANAGER_ID, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_PATH, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_CHECKSUM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_TIME, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + }...) + } + + return columns + } + + // cannot sort when querying provisioned dashboards + if sortByField != "" { + columns = append(columns, &resourcepb.ResourceTableColumnDefinition{ + Name: sortByField, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }) + } + + return columns +} + +func (c *DashboardSearchClient) createCommonCells(title, folderUID string, id int64, tags []byte) [][]byte { + return [][]byte{ + []byte(title), + []byte(folderUID), + tags, + []byte(strconv.FormatInt(id, 10)), + } +} + +func (c *DashboardSearchClient) createBaseCells(dashboard dashboards.DashboardSearchProjection, sortByField string) ([][]byte, error) { + tags, err := json.Marshal(dashboard.Tags) + if err != nil { + return nil, err + } + + cells := c.createCommonCells(dashboard.Title, dashboard.FolderUID, dashboard.ID, tags) + + if sortByField != "" { + cells = append(cells, []byte(strconv.FormatInt(dashboard.SortMeta, 10))) + } + + return cells, nil +} + +func (c *DashboardSearchClient) createProvisioningCells(dashboard *dashboards.Dashboard, query *dashboards.FindPersistedDashboardsQuery) [][]byte { + cells := c.createCommonCells(dashboard.Title, dashboard.FolderUID, dashboard.ID, []byte("[]")) + return append(cells, []byte(query.ManagedBy)) +} + +func (c *DashboardSearchClient) createDetailedProvisioningCells(dashboard *dashboards.DashboardProvisioningSearchResults, query *dashboards.FindPersistedDashboardsQuery) [][]byte { + cells := c.createCommonCells(dashboard.Dashboard.Title, dashboard.Dashboard.FolderUID, dashboard.Dashboard.ID, []byte("[]")) + return append(cells, + []byte(query.ManagedBy), + []byte(dashboard.Provisioner), + []byte(dashboard.ExternalID), + []byte(dashboard.CheckSum), + []byte(strconv.FormatInt(dashboard.ProvisionUpdate, 10)), + ) +} diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go index eeb0a18c469..da6b58e2f84 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go @@ -420,8 +420,12 @@ func TestDashboardSearchClient_Search(t *testing.T) { }) t.Run("Should retrieve dashboards by provisioner name through a different function", func(t *testing.T) { - mockStore.On("GetProvisionedDashboardsByName", mock.Anything, "test", mock.Anything).Return([]*dashboards.Dashboard{ - {UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"}, + mockStore.On("GetProvisionedDashboardsByName", mock.Anything, "test", mock.Anything).Return([]*dashboards.DashboardProvisioningSearchResults{ + { + Dashboard: dashboards.Dashboard{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"}, + ExternalID: "test", + Provisioner: string(utils.ManagerKindClassicFP), // nolint:staticcheck + }, }, nil).Once() req := &resourcepb.ResourceSearchRequest{ diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index 97c484fa5f9..586fd3a0e0b 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -86,9 +86,9 @@ type Store interface { GetDashboardsByPluginID(ctx context.Context, query *GetDashboardsByPluginIDQuery) ([]*Dashboard, error) GetDashboardTags(ctx context.Context, query *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error) GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) - GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) - GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error) - GetProvisionedDashboardsByName(ctx context.Context, name string, orgID int64) ([]*Dashboard, error) + GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioningSearchResults, error) + GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioningSearchResults, error) + GetProvisionedDashboardsByName(ctx context.Context, name string, orgID int64) ([]*DashboardProvisioningSearchResults, error) GetOrphanedProvisionedDashboards(ctx context.Context, notIn []string, orgID int64) ([]*Dashboard, error) SaveDashboard(ctx context.Context, cmd SaveDashboardCommand) (*Dashboard, error) SaveProvisionedDashboard(ctx context.Context, cmd SaveDashboardCommand, provisioning *DashboardProvisioning) (*Dashboard, error) diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 4830b564543..633e8610033 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -171,27 +171,34 @@ func (d *dashboardStore) ValidateDashboardBeforeSave(ctx context.Context, dash * return isParentFolderChanged, nil } -func (d *dashboardStore) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*dashboards.DashboardProvisioning, error) { +func (d *dashboardStore) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*dashboards.DashboardProvisioningSearchResults, error) { ctx, span := tracer.Start(ctx, "dashboards.database.GetProvisionedDataByDashboardID") defer span.End() - var data dashboards.DashboardProvisioning + data := []*dashboards.DashboardProvisioningSearchResults{} err := d.store.WithDbSession(ctx, func(sess *db.Session) error { - _, err := sess.Where("dashboard_id = ?", dashboardID).Get(&data) - return err + return sess.Table(`dashboard`). + Join(`INNER`, `dashboard_provisioning`, `dashboard.id = dashboard_provisioning.dashboard_id`). + Where(`dashboard_provisioning.dashboard_id = ?`, dashboardID). + Select("dashboard.*, dashboard_provisioning.name, dashboard_provisioning.external_id, dashboard_provisioning.updated as provisioning_updated, dashboard_provisioning.check_sum"). + Find(&data) }) + if err != nil { + return nil, err + } - if data.DashboardID == 0 { + if len(data) == 0 { return nil, nil } - return &data, err + + return data[0], nil } -func (d *dashboardStore) GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*dashboards.DashboardProvisioning, error) { +func (d *dashboardStore) GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*dashboards.DashboardProvisioningSearchResults, error) { ctx, span := tracer.Start(ctx, "dashboards.database.GetProvisionedDataByDashboardUID") defer span.End() - var provisionedDashboard dashboards.DashboardProvisioning + provisionedDashboard := []*dashboards.DashboardProvisioningSearchResults{} err := d.store.WithDbSession(ctx, func(sess *db.Session) error { var dashboard dashboards.Dashboard exists, err := sess.Where("org_id = ? AND uid = ?", orgID, dashboardUID).Get(&dashboard) @@ -201,16 +208,22 @@ func (d *dashboardStore) GetProvisionedDataByDashboardUID(ctx context.Context, o if !exists { return dashboards.ErrDashboardNotFound } - exists, err = sess.Where("dashboard_id = ?", dashboard.ID).Get(&provisionedDashboard) - if err != nil { - return err - } - if !exists { - return dashboards.ErrProvisionedDashboardNotFound - } - return nil + + return sess.Table(`dashboard`). + Join(`INNER`, `dashboard_provisioning`, `dashboard.id = dashboard_provisioning.dashboard_id`). + Where(`dashboard_provisioning.dashboard_id = ?`, dashboard.ID). + Select("dashboard.*, dashboard_provisioning.name, dashboard_provisioning.external_id, dashboard_provisioning.updated as provisioning_updated, dashboard_provisioning.check_sum"). + Find(&provisionedDashboard) }) - return &provisionedDashboard, err + if err != nil { + return nil, err + } + + if len(provisionedDashboard) == 0 { + return nil, dashboards.ErrProvisionedDashboardNotFound + } + + return provisionedDashboard[0], nil } func (d *dashboardStore) GetProvisionedDashboardData(ctx context.Context, name string) ([]*dashboards.DashboardProvisioning, error) { @@ -224,15 +237,17 @@ func (d *dashboardStore) GetProvisionedDashboardData(ctx context.Context, name s return result, err } -func (d *dashboardStore) GetProvisionedDashboardsByName(ctx context.Context, name string, orgID int64) ([]*dashboards.Dashboard, error) { +func (d *dashboardStore) GetProvisionedDashboardsByName(ctx context.Context, name string, orgID int64) ([]*dashboards.DashboardProvisioningSearchResults, error) { ctx, span := tracer.Start(ctx, "dashboards.database.GetProvisionedDashboardsByName") defer span.End() - dashes := []*dashboards.Dashboard{} + dashes := []*dashboards.DashboardProvisioningSearchResults{} err := d.store.WithDbSession(ctx, func(sess *db.Session) error { return sess.Table(`dashboard`). Join(`INNER`, `dashboard_provisioning`, `dashboard.id = dashboard_provisioning.dashboard_id`). - Where(`dashboard_provisioning.name = ? AND dashboard.org_id = ?`, name, orgID).Find(&dashes) + Where(`dashboard_provisioning.name = ? AND dashboard.org_id = ?`, name, orgID). + Select("dashboard.*, dashboard_provisioning.name, dashboard_provisioning.external_id, dashboard_provisioning.updated as provisioning_updated, dashboard_provisioning.check_sum"). + Find(&dashes) }) if err != nil { return nil, err diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index ff148d1e0a2..547737c76fe 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -297,15 +297,15 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { require.Equal(t, res[0], provisioningData) // get dashboards within the provisioner - dashs, err := dashboardStore.GetProvisionedDashboardsByName(context.Background(), "test", 1) + provisionedDashes, err := dashboardStore.GetProvisionedDashboardsByName(context.Background(), "test", 1) require.NoError(t, err) - require.Len(t, dashs, 1) - dashs, err = dashboardStore.GetProvisionedDashboardsByName(context.Background(), "test", 2) + require.Len(t, provisionedDashes, 1) + provisionedDashes, err = dashboardStore.GetProvisionedDashboardsByName(context.Background(), "test", 2) require.NoError(t, err) - require.Len(t, dashs, 0) + require.Len(t, provisionedDashes, 0) // find dashboards not within that provisioner - dashs, err = dashboardStore.GetOrphanedProvisionedDashboards(context.Background(), []string{"test"}, 1) + dashs, err := dashboardStore.GetOrphanedProvisionedDashboards(context.Background(), []string{"test"}, 1) require.NoError(t, err) require.Len(t, dashs, 1) dashs, err = dashboardStore.GetOrphanedProvisionedDashboards(context.Background(), []string{"test"}, 2) diff --git a/pkg/services/dashboards/models.go b/pkg/services/dashboards/models.go index a071c5bd298..0dc8a4e3e8b 100644 --- a/pkg/services/dashboards/models.go +++ b/pkg/services/dashboards/models.go @@ -239,6 +239,14 @@ type DeleteOrphanedProvisionedDashboardsCommand struct { ReaderNames []string } +type DashboardProvisioningSearchResults struct { + Dashboard Dashboard `xorm:"extends"` + Provisioner string `xorm:"name"` + ExternalID string `xorm:"external_id"` + CheckSum string `xorm:"check_sum"` + ProvisionUpdate int64 `xorm:"provisioning_updated"` +} + // // QUERIES // diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index c5333e6c0bc..b71007e7cfc 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -7,7 +7,6 @@ import ( "fmt" "strconv" "strings" - "sync" "time" "github.com/google/uuid" @@ -16,7 +15,6 @@ import ( "go.opentelemetry.io/otel/attribute" "golang.org/x/exp/maps" "golang.org/x/exp/slices" - "golang.org/x/sync/errgroup" apierrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -528,33 +526,19 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context, } results := []*dashboards.DashboardProvisioning{} - var mu sync.Mutex - g, ctx := errgroup.WithContext(ctx) - g.SetLimit(provisioningConcurrencyLimit) for _, org := range orgs { - func(orgID int64) { - g.Go(func() error { - res, err := dr.searchProvisionedDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{ - ManagedBy: utils.ManagerKindClassicFP, // nolint:staticcheck - ManagerIdentity: name, - OrgId: orgID, - }) - if err != nil { - return err - } + res, err := dr.searchProvisionedDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{ + ManagedBy: utils.ManagerKindClassicFP, // nolint:staticcheck + ManagerIdentity: name, + OrgId: org.ID, + }) + if err != nil { + return nil, err + } - mu.Lock() - for _, r := range res { - results = append(results, &r.DashboardProvisioning) - } - mu.Unlock() - return nil - }) - }(org.ID) - } - - if err := g.Wait(); err != nil { - return nil, err + for _, r := range res { + results = append(results, &r.DashboardProvisioning) + } } return results, nil @@ -595,7 +579,21 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx con return nil, nil } - return dr.dashboardStore.GetProvisionedDataByDashboardID(ctx, dashboardID) + data, err := dr.dashboardStore.GetProvisionedDataByDashboardID(ctx, dashboardID) + if err != nil { + return nil, err + } + if data == nil { + return nil, nil + } + + return &dashboards.DashboardProvisioning{ + DashboardID: data.Dashboard.ID, + Name: data.Provisioner, + ExternalID: data.ExternalID, + CheckSum: data.CheckSum, + Updated: data.ProvisionUpdate, + }, nil } func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*dashboards.DashboardProvisioning, error) { @@ -622,7 +620,21 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx co return nil, nil } - return dr.dashboardStore.GetProvisionedDataByDashboardUID(ctx, orgID, dashboardUID) + data, err := dr.dashboardStore.GetProvisionedDataByDashboardUID(ctx, orgID, dashboardUID) + if err != nil { + return nil, err + } + if data == nil { + return nil, nil + } + + return &dashboards.DashboardProvisioning{ + DashboardID: data.Dashboard.ID, + Name: data.Provisioner, + ExternalID: data.ExternalID, + CheckSum: data.CheckSum, + Updated: data.ProvisionUpdate, + }, nil } func (dr *DashboardServiceImpl) ValidateBasicDashboardProperties(title string, uid string, message string) error { @@ -2040,8 +2052,6 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex if query.Title != "" { // allow wildcard search request.Query = "*" + strings.ToLower(query.Title) + "*" - // if using query, you need to specify the fields you want - request.Fields = dashboardsearch.IncludeFields } if len(query.Tags) > 0 { @@ -2072,6 +2082,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex request.Limit = query.Limit request.Page = query.Page request.Offset = (query.Page - 1) * query.Limit // only relevant when running in modes 3+ + request.Fields = dashboardsearch.IncludeFields namespace := dr.k8sclient.GetNamespace(query.OrgId) var err error @@ -2140,60 +2151,23 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex span.SetAttributes(attribute.Int("hits", len(searchResults.Hits))) - // loop through all hits concurrently to get the repo information (if set due to file provisioning) dashs := make([]*dashboardProvisioningWithUID, 0) - var mu sync.Mutex - g, ctx := errgroup.WithContext(ctx) - g.SetLimit(provisioningConcurrencyLimit) - for _, h := range searchResults.Hits { - func(hit dashboardv0.DashboardHit) { - g.Go(func() error { - out, err := dr.k8sclient.Get(ctx, hit.Name, query.OrgId, v1.GetOptions{}) - if err != nil { - return err - } else if out == nil { - return dashboards.ErrDashboardNotFound - } + for _, hit := range searchResults.Hits { + if utils.ParseManagerKindString(hit.Field.GetNestedString(resource.SEARCH_FIELD_MANAGER_KIND)) != utils.ManagerKindClassicFP { // nolint:staticcheck + continue + } - meta, err := utils.MetaAccessor(out) - if err != nil { - return err - } - - m, ok := meta.GetManagerProperties() - if !ok || m.Kind != utils.ManagerKindClassicFP { // nolint:staticcheck - return nil - } - - source, ok := meta.GetSourceProperties() - if !ok { - return nil - } - - provisioning := &dashboardProvisioningWithUID{ - DashboardProvisioning: dashboards.DashboardProvisioning{ - Name: m.Identity, - ExternalID: source.Path, - CheckSum: source.Checksum, - DashboardID: meta.GetDeprecatedInternalID(), // nolint:staticcheck - }, - DashboardUID: hit.Name, - } - if source.TimestampMillis > 0 { - provisioning.Updated = time.UnixMilli(source.TimestampMillis).Unix() - } - - mu.Lock() - dashs = append(dashs, provisioning) - mu.Unlock() - - return nil - }) - }(h) - } - - if err := g.Wait(); err != nil { - return nil, err + provisioning := &dashboardProvisioningWithUID{ + DashboardProvisioning: dashboards.DashboardProvisioning{ + Name: hit.Field.GetNestedString(resource.SEARCH_FIELD_MANAGER_ID), + ExternalID: hit.Field.GetNestedString(resource.SEARCH_FIELD_SOURCE_PATH), + CheckSum: hit.Field.GetNestedString(resource.SEARCH_FIELD_SOURCE_CHECKSUM), + Updated: hit.Field.GetNestedInt64(resource.SEARCH_FIELD_SOURCE_TIME), + DashboardID: hit.Field.GetNestedInt64(utils.LabelKeyDeprecatedInternalID), // nolint:staticcheck + }, + DashboardUID: hit.Name, + } + dashs = append(dashs, provisioning) } return dashs, nil diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 385f137a805..23c03dda00a 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -141,7 +141,7 @@ func TestDashboardService(t *testing.T) { t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) { fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() - fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioning{}, nil).Once() + fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once() dto.Dashboard = dashboards.NewDashboard("Dash") dto.Dashboard.SetID(3) @@ -194,7 +194,7 @@ func TestDashboardService(t *testing.T) { dto := &dashboards.SaveDashboardDTO{} t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) { - fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioning{}, nil).Once() + fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once() dto.Dashboard = dashboards.NewDashboard("Dash") dto.Dashboard.SetID(3) @@ -214,7 +214,7 @@ func TestDashboardService(t *testing.T) { }) t.Run("DeleteDashboard should fail to delete it when provisioning information is missing", func(t *testing.T) { - fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioning{}, nil).Once() + fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once() err := service.DeleteDashboard(context.Background(), 1, "", 1) require.Equal(t, err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard) }) @@ -542,30 +542,6 @@ func TestGetProvisionedDashboardData(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) provisioningTimestamp := int64(1234567) 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]interface{}{ - "apiVersion": dashboardv0.DashboardResourceInfo.GroupVersion().String(), - "kind": dashboardv0.DashboardResourceInfo.GroupVersionKind().Kind, - "metadata": map[string]interface{}{ - "name": "uid", - "labels": map[string]interface{}{ - utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck - }, - "annotations": map[string]interface{}{ - utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck - utils.AnnoKeyManagerIdentity: "test", - utils.AnnoKeySourceChecksum: "hash", - utils.AnnoKeySourcePath: "path/to/file", - utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()), - }, - }, - "spec": map[string]interface{}{ - "test": "test", - "version": int64(1), - "title": "testing slugify", - }, - }, - }, nil).Once() repo := "test" k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool { @@ -594,6 +570,30 @@ func TestGetProvisionedDashboardData(t *testing.T) { Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING, }, + { + Name: resource.SEARCH_FIELD_LEGACY_ID, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_KIND, // nolint:staticcheck + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_ID, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_PATH, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_CHECKSUM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_TIME, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, }, Rows: []*resourcepb.ResourceTableRow{ { @@ -604,6 +604,12 @@ func TestGetProvisionedDashboardData(t *testing.T) { Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), + []byte("1"), + []byte(string(utils.ManagerKindClassicFP)), // nolint:staticcheck + []byte(repo), + []byte("path/to/file"), + []byte("hash"), + []byte("1234567"), }, }, }, @@ -638,7 +644,7 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) { t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() - fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, int64(1)).Return(&dashboards.DashboardProvisioning{}, nil).Once() + fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, int64(1)).Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once() dashboard, err := service.GetProvisionedDashboardDataByDashboardID(context.Background(), 1) require.NoError(t, err) require.NotNil(t, dashboard) @@ -649,28 +655,6 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) provisioningTimestamp := int64(1234567) 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]interface{}{ - "apiVersion": dashboardv0.DashboardResourceInfo.GroupVersion().String(), - "kind": dashboardv0.DashboardResourceInfo.GroupVersionKind().Kind, - "metadata": map[string]interface{}{ - "name": "uid", - "labels": map[string]interface{}{ - utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck - }, - "annotations": map[string]interface{}{ - utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck - utils.AnnoKeyManagerIdentity: "test", - utils.AnnoKeySourceChecksum: "hash", - utils.AnnoKeySourcePath: "path/to/file", - utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()), - }, - }, - "spec": map[string]interface{}{ - "test": "test", - "version": int64(1), - "title": "testing slugify", - }, - }}, nil) k8sCliMock.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resourcepb.ResourceSearchResponse{ Results: &resourcepb.ResourceTable{ Columns: []*resourcepb.ResourceTableColumnDefinition{}, @@ -689,6 +673,30 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) { Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING, }, + { + Name: resource.SEARCH_FIELD_LEGACY_ID, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_KIND, // nolint:staticcheck + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_ID, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_PATH, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_CHECKSUM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_TIME, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, }, Rows: []*resourcepb.ResourceTableRow{ { @@ -699,6 +707,12 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) { Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), + []byte("1"), + []byte(string(utils.ManagerKindClassicFP)), // nolint:staticcheck + []byte("test"), + []byte("path/to/file"), + []byte("hash"), + []byte("1234567"), }, }, }, @@ -732,7 +746,7 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) { t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() - fakeStore.On("GetProvisionedDataByDashboardUID", mock.Anything, int64(1), "test").Return(&dashboards.DashboardProvisioning{}, nil).Once() + fakeStore.On("GetProvisionedDataByDashboardUID", mock.Anything, int64(1), "test").Return(&dashboards.DashboardProvisioningSearchResults{}, nil).Once() dashboard, err := service.GetProvisionedDashboardDataByDashboardUID(context.Background(), 1, "test") require.NoError(t, err) require.NotNil(t, dashboard) @@ -743,28 +757,6 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) provisioningTimestamp := int64(1234567) 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]interface{}{ - "apiVersion": dashboardv0.DashboardResourceInfo.GroupVersion().String(), - "kind": dashboardv0.DashboardResourceInfo.GroupVersionKind().Kind, - "metadata": map[string]interface{}{ - "name": "uid", - "labels": map[string]interface{}{ - utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck - }, - "annotations": map[string]interface{}{ - utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck - utils.AnnoKeyManagerIdentity: "test", - utils.AnnoKeySourceChecksum: "hash", - utils.AnnoKeySourcePath: "path/to/file", - utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()), - }, - }, - "spec": map[string]interface{}{ - "test": "test", - "version": int64(1), - "title": "testing slugify", - }, - }}, nil).Once() k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{ Results: &resourcepb.ResourceTable{ Columns: []*resourcepb.ResourceTableColumnDefinition{ @@ -776,6 +768,30 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) { Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING, }, + { + Name: resource.SEARCH_FIELD_LEGACY_ID, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_KIND, // nolint:staticcheck + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_ID, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_PATH, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_CHECKSUM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_TIME, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, }, Rows: []*resourcepb.ResourceTableRow{ { @@ -786,6 +802,12 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) { Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), + []byte("1"), + []byte(string(utils.ManagerKindClassicFP)), // nolint:staticcheck + []byte("test"), + []byte("path/to/file"), + []byte("hash"), + []byte("1234567"), }, }, }, @@ -836,44 +858,6 @@ func TestDeleteOrphanedProvisionedDashboards(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) - k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ - "metadata": map[string]any{ - "name": "uid", - "annotations": map[string]any{ - utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck - utils.AnnoKeyManagerIdentity: "orphaned", - utils.AnnoKeySourceChecksum: "hash", - utils.AnnoKeySourcePath: "path/to/file", - utils.AnnoKeySourceTimestamp: "2025-01-01T00:00:00Z", - }, - }, - "spec": map[string]any{}, - }}, nil).Once() - // should not delete this one, because it does not start with "file:" - k8sCliMock.On("Get", mock.Anything, "uid2", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ - "metadata": map[string]any{ - "name": "uid2", - "annotations": map[string]any{ - utils.AnnoKeyManagerKind: string(utils.ManagerKindPlugin), - utils.AnnoKeyManagerIdentity: "app", - }, - }, - "spec": map[string]any{}, - }}, nil).Once() - - k8sCliMock.On("Get", mock.Anything, "uid3", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ - "metadata": map[string]any{ - "name": "uid3", - "annotations": map[string]any{ - utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck - utils.AnnoKeyManagerIdentity: "orphaned", - utils.AnnoKeySourceChecksum: "hash", - utils.AnnoKeySourcePath: "path/to/file", - utils.AnnoKeySourceTimestamp: "2025-01-01T00:00:00Z", - }, - }, - "spec": map[string]any{}, - }}, nil).Once() k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool { // nolint:staticcheck return req.Options.Fields[0].Key == "manager.kind" && req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && req.Options.Fields[1].Key == "manager.id" && req.Options.Fields[1].Values[0] == "test" && req.Options.Fields[1].Operator == "notin" @@ -888,6 +872,26 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING, }, + { + Name: resource.SEARCH_FIELD_MANAGER_KIND, // nolint:staticcheck + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_ID, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_PATH, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_CHECKSUM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_TIME, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, }, Rows: []*resourcepb.ResourceTableRow{ { @@ -898,6 +902,11 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), + []byte(string(utils.ManagerKindClassicFP)), // nolint:staticcheck + []byte("orphaned"), + []byte("path/to/file"), + []byte("hash"), + []byte("1234567"), }, }, }, @@ -919,6 +928,26 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING, }, + { + Name: resource.SEARCH_FIELD_MANAGER_KIND, // nolint:staticcheck + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_ID, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_PATH, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_CHECKSUM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_TIME, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, }, Rows: []*resourcepb.ResourceTableRow{ { @@ -929,6 +958,11 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { Cells: [][]byte{ []byte("Dashboard 2"), []byte("folder 2"), + []byte(string(utils.ManagerKindPlugin)), + []byte("app"), + []byte(""), + []byte(""), + []byte(""), }, }, { @@ -939,6 +973,11 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { Cells: [][]byte{ []byte("Dashboard 3"), []byte("folder 3"), + []byte(string(utils.ManagerKindClassicFP)), // nolint:staticcheck + []byte("orphaned"), + []byte("path/to/file"), + []byte("hash"), + []byte("1234567"), }, }, }, @@ -971,34 +1010,8 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { log: log.NewNopLogger(), } ctx, k8sCliMock := setupK8sDashboardTests(singleOrgService) - provisioningTimestamp := int64(1234567) - // Call to searchProvisionedDashboardsThroughK8s() 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]interface{}{ - "apiVersion": dashboardv0.DashboardResourceInfo.GroupVersion().String(), - "kind": dashboardv0.DashboardResourceInfo.GroupVersionKind().Kind, - "metadata": map[string]interface{}{ - "name": "uid", - "labels": map[string]interface{}{ - utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck - }, - "annotations": map[string]interface{}{ - utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck - utils.AnnoKeyManagerIdentity: "test", - utils.AnnoKeySourceChecksum: "hash", - utils.AnnoKeySourcePath: "path/to/file", - utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()), - }, - }, - "spec": map[string]interface{}{ - "test": "test", - "version": int64(1), - "title": "testing slugify", - }, - }, - }, nil).Once() k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool { // make sure the kind is added to the query return req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && // nolint:staticcheck @@ -1014,6 +1027,26 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING, }, + { + Name: resource.SEARCH_FIELD_MANAGER_KIND, // nolint:staticcheck + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_ID, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_PATH, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_CHECKSUM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_TIME, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, }, Rows: []*resourcepb.ResourceTableRow{ { @@ -1024,6 +1057,11 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), + []byte(string(utils.ManagerKindClassicFP)), // nolint:staticcheck + []byte("orphaned"), + []byte("path/to/file"), + []byte("hash"), + []byte("1234567"), }, }, }, @@ -2328,25 +2366,6 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) { OrgId: 1, } provisioningTimestamp := int64(1234567) - dashboardUnstructuredProvisioned := unstructured.Unstructured{Object: map[string]any{ - "metadata": map[string]any{ - "name": "uid", - "annotations": map[string]any{ - utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck - utils.AnnoKeyManagerIdentity: "test", - utils.AnnoKeySourceChecksum: "hash", - utils.AnnoKeySourcePath: "path/to/file", - utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()), - }, - }, - "spec": map[string]any{}, - }} - dashboardUnstructuredNotProvisioned := unstructured.Unstructured{Object: map[string]any{ - "metadata": map[string]any{ - "name": "uid2", - }, - "spec": map[string]any{}, - }} k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.ResourceSearchResponse{ Results: &resourcepb.ResourceTable{ @@ -2359,6 +2378,26 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) { Name: "folder", Type: resourcepb.ResourceTableColumnDefinition_STRING, }, + { + Name: resource.SEARCH_FIELD_MANAGER_KIND, // nolint:staticcheck + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_MANAGER_ID, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_PATH, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_CHECKSUM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: resource.SEARCH_FIELD_SOURCE_TIME, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, }, Rows: []*resourcepb.ResourceTableRow{ { @@ -2368,25 +2407,18 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) { }, Cells: [][]byte{ []byte("Dashboard 1"), - []byte("folder1"), - }, - }, - { - Key: &resourcepb.ResourceKey{ - Name: "uid2", - Resource: "dashboard", - }, - Cells: [][]byte{ - []byte("Dashboard 2"), - []byte("folder2"), + []byte("folder 1"), + []byte(string(utils.ManagerKindClassicFP)), // nolint:staticcheck + []byte("test"), + []byte("path/to/file"), + []byte("hash"), + []byte("1234567"), }, }, }, }, TotalHits: 1, }, nil) - k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructuredProvisioned, nil).Once() - k8sCliMock.On("Get", mock.Anything, "uid2", mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructuredNotProvisioned, nil).Once() res, err := service.searchProvisionedDashboardsThroughK8s(ctx, query) require.NoError(t, err) assert.Equal(t, []*dashboardProvisioningWithUID{ diff --git a/pkg/services/dashboards/store_mock.go b/pkg/services/dashboards/store_mock.go index 6ce6b19e8ef..dab3ae98ae0 100644 --- a/pkg/services/dashboards/store_mock.go +++ b/pkg/services/dashboards/store_mock.go @@ -461,23 +461,23 @@ func (_m *FakeDashboardStore) GetProvisionedDashboardData(ctx context.Context, n } // GetProvisionedDashboardsByName provides a mock function with given fields: ctx, name, orgID -func (_m *FakeDashboardStore) GetProvisionedDashboardsByName(ctx context.Context, name string, orgID int64) ([]*Dashboard, error) { +func (_m *FakeDashboardStore) GetProvisionedDashboardsByName(ctx context.Context, name string, orgID int64) ([]*DashboardProvisioningSearchResults, error) { ret := _m.Called(ctx, name, orgID) if len(ret) == 0 { panic("no return value specified for GetProvisionedDashboardsByName") } - var r0 []*Dashboard + var r0 []*DashboardProvisioningSearchResults var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, int64) ([]*Dashboard, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, int64) ([]*DashboardProvisioningSearchResults, error)); ok { return rf(ctx, name, orgID) } - if rf, ok := ret.Get(0).(func(context.Context, string, int64) []*Dashboard); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, int64) []*DashboardProvisioningSearchResults); ok { r0 = rf(ctx, name, orgID) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*Dashboard) + r0 = ret.Get(0).([]*DashboardProvisioningSearchResults) } } @@ -491,23 +491,23 @@ func (_m *FakeDashboardStore) GetProvisionedDashboardsByName(ctx context.Context } // GetProvisionedDataByDashboardID provides a mock function with given fields: ctx, dashboardID -func (_m *FakeDashboardStore) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) { +func (_m *FakeDashboardStore) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioningSearchResults, error) { ret := _m.Called(ctx, dashboardID) if len(ret) == 0 { panic("no return value specified for GetProvisionedDataByDashboardID") } - var r0 *DashboardProvisioning + var r0 *DashboardProvisioningSearchResults var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int64) (*DashboardProvisioning, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, int64) (*DashboardProvisioningSearchResults, error)); ok { return rf(ctx, dashboardID) } - if rf, ok := ret.Get(0).(func(context.Context, int64) *DashboardProvisioning); ok { + if rf, ok := ret.Get(0).(func(context.Context, int64) *DashboardProvisioningSearchResults); ok { r0 = rf(ctx, dashboardID) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*DashboardProvisioning) + r0 = ret.Get(0).(*DashboardProvisioningSearchResults) } } @@ -521,23 +521,23 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardID(ctx context.Contex } // GetProvisionedDataByDashboardUID provides a mock function with given fields: ctx, orgID, dashboardUID -func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error) { +func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioningSearchResults, error) { ret := _m.Called(ctx, orgID, dashboardUID) if len(ret) == 0 { panic("no return value specified for GetProvisionedDataByDashboardUID") } - var r0 *DashboardProvisioning + var r0 *DashboardProvisioningSearchResults var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*DashboardProvisioning, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*DashboardProvisioningSearchResults, error)); ok { return rf(ctx, orgID, dashboardUID) } - if rf, ok := ret.Get(0).(func(context.Context, int64, string) *DashboardProvisioning); ok { + if rf, ok := ret.Get(0).(func(context.Context, int64, string) *DashboardProvisioningSearchResults); ok { r0 = rf(ctx, orgID, dashboardUID) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*DashboardProvisioning) + r0 = ret.Get(0).(*DashboardProvisioningSearchResults) } } diff --git a/pkg/storage/unified/resource/document.go b/pkg/storage/unified/resource/document.go index 3c5bbc6e5c0..d8e07a58e0f 100644 --- a/pkg/storage/unified/resource/document.go +++ b/pkg/storage/unified/resource/document.go @@ -399,7 +399,25 @@ func StandardSearchFields() SearchableDocumentFields { Type: resourcepb.ResourceTableColumnDefinition_STRING, Description: "Type of manager, which is responsible for managing the resource", }, + // TODO: below fields only need to be returned from search, but do not need to be searchable + { + Name: SEARCH_FIELD_MANAGER_ID, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: SEARCH_FIELD_SOURCE_TIME, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + }, + { + Name: SEARCH_FIELD_SOURCE_PATH, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, + { + Name: SEARCH_FIELD_SOURCE_CHECKSUM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + }, }) + if err != nil { panic("failed to initialize standard search fields") }