Dashboard Provisioning: Reduce db load (#106114)
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user