Dashboards: Add Dashboard API Validation tests and fix underlying issues (#103502)

This commit is contained in:
Marco de Abreu
2025-04-07 05:44:28 +03:00
committed by GitHub
parent 427715b070
commit b2f80d4dca
16 changed files with 1521 additions and 240 deletions
+5
View File
@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/search/model"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// DashboardService is a service for operating on dashboards.
@@ -33,6 +34,10 @@ type DashboardService interface {
GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*Dashboard, error)
CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error
CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error)
SetDefaultPermissions(ctx context.Context, dto *SaveDashboardDTO, dash *Dashboard, provisioned bool)
UnstructuredToLegacyDashboard(ctx context.Context, item *unstructured.Unstructured, orgID int64) (*Dashboard, error)
ValidateDashboardRefreshInterval(minRefreshInterval string, targetRefreshInterval string) error
ValidateBasicDashboardProperties(title string, uid string, message string) error
}
type PermissionsRegistrationService interface {
@@ -1,4 +1,4 @@
// Code generated by mockery v2.52.2. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package dashboards
@@ -9,6 +9,8 @@ import (
mock "github.com/stretchr/testify/mock"
model "github.com/grafana/grafana/pkg/services/search/model"
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// FakeDashboardService is an autogenerated mock type for the DashboardService type
@@ -46,6 +48,24 @@ func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, d
return r0, r1
}
// CleanUpDashboard provides a mock function with given fields: ctx, dashboardUID, orgId
func (_m *FakeDashboardService) CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error {
ret := _m.Called(ctx, dashboardUID, orgId)
if len(ret) == 0 {
panic("no return value specified for CleanUpDashboard")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok {
r0 = rf(ctx, dashboardUID, orgId)
} else {
r0 = ret.Error(0)
}
return r0
}
// CountDashboardsInOrg provides a mock function with given fields: ctx, orgID
func (_m *FakeDashboardService) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) {
ret := _m.Called(ctx, orgID)
@@ -438,17 +458,70 @@ func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *Fin
return r0, r1
}
// CleanUpDashboard provides a mock function with given fields: ctx, dashboardUID, orgId
func (_m *FakeDashboardService) CleanUpDashboard(ctx context.Context, dashboardUID string, orgId int64) error {
ret := _m.Called(ctx, dashboardUID, orgId)
// SetDefaultPermissions provides a mock function with given fields: ctx, dto, dash, provisioned
func (_m *FakeDashboardService) SetDefaultPermissions(ctx context.Context, dto *SaveDashboardDTO, dash *Dashboard, provisioned bool) {
_m.Called(ctx, dto, dash, provisioned)
}
// UnstructuredToLegacyDashboard provides a mock function with given fields: ctx, item, orgID
func (_m *FakeDashboardService) UnstructuredToLegacyDashboard(ctx context.Context, item *unstructured.Unstructured, orgID int64) (*Dashboard, error) {
ret := _m.Called(ctx, item, orgID)
if len(ret) == 0 {
panic("no return value specified for CleanUpDashboard")
panic("no return value specified for UnstructuredToLegacyDashboard")
}
var r0 *Dashboard
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *unstructured.Unstructured, int64) (*Dashboard, error)); ok {
return rf(ctx, item, orgID)
}
if rf, ok := ret.Get(0).(func(context.Context, *unstructured.Unstructured, int64) *Dashboard); ok {
r0 = rf(ctx, item, orgID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*Dashboard)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *unstructured.Unstructured, int64) error); ok {
r1 = rf(ctx, item, orgID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ValidateBasicDashboardProperties provides a mock function with given fields: title, uid, message
func (_m *FakeDashboardService) ValidateBasicDashboardProperties(title string, uid string, message string) error {
ret := _m.Called(title, uid, message)
if len(ret) == 0 {
panic("no return value specified for ValidateBasicDashboardProperties")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, int64) error); ok {
r0 = rf(ctx, dashboardUID, orgId)
if rf, ok := ret.Get(0).(func(string, string, string) error); ok {
r0 = rf(title, uid, message)
} else {
r0 = ret.Error(0)
}
return r0
}
// ValidateDashboardRefreshInterval provides a mock function with given fields: minRefreshInterval, targetRefreshInterval
func (_m *FakeDashboardService) ValidateDashboardRefreshInterval(minRefreshInterval string, targetRefreshInterval string) error {
ret := _m.Called(minRefreshInterval, targetRefreshInterval)
if len(ret) == 0 {
panic("no return value specified for ValidateDashboardRefreshInterval")
}
var r0 error
if rf, ok := ret.Get(0).(func(string, string) error); ok {
r0 = rf(minRefreshInterval, targetRefreshInterval)
} else {
r0 = ret.Error(0)
}
+92 -130
View File
@@ -70,22 +70,98 @@ func (d *dashboardStore) emitEntityEvent() bool {
return d.features != nil && d.features.IsEnabledGlobally(featuremgmt.FlagPanelTitleSearch)
}
// TODO: once the folder service removes usage of this function, remove it here. The dashboard service now implements this
// on the service level for dashboards.
func (d *dashboardStore) ValidateDashboardBeforeSave(ctx context.Context, dashboard *dashboards.Dashboard, overwrite bool) (bool, error) {
func (d *dashboardStore) ValidateDashboardBeforeSave(ctx context.Context, dash *dashboards.Dashboard, overwrite bool) (bool, error) {
ctx, span := tracer.Start(ctx, "dashboards.database.ValidateDashboardBeforesave")
defer span.End()
isParentFolderChanged := false
err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
var err error
isParentFolderChanged, err = getExistingDashboardByIDOrUIDForUpdate(sess, dashboard, overwrite)
if err != nil {
return err
dashWithIdExists := false
var existingById dashboards.Dashboard
// we don't save FolderID in kubernetes object when saving through k8s
// this block guarantees we save dashboards with folder_id and folder_uid in those cases
if !dash.IsFolder && dash.FolderUID != "" && dash.FolderID == 0 { // nolint:staticcheck
var existing dashboards.Dashboard
folderIdFound, err := sess.Where("uid=? AND org_id=?", dash.FolderUID, dash.OrgID).Get(&existing)
if err != nil {
return err
}
if folderIdFound {
dash.FolderID = existing.ID // nolint:staticcheck
}
}
if dash.ID > 0 {
var err error
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.ID, dash.OrgID).Get(&existingById)
if err != nil {
return fmt.Errorf("SQL query for existing dashboard by ID failed: %w", err)
}
if !dashWithIdExists {
return dashboards.ErrDashboardNotFound
}
if dash.UID == "" {
dash.SetUID(existingById.UID)
}
}
dashWithUidExists := false
var existingByUid dashboards.Dashboard
if dash.UID != "" {
var err error
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgID, dash.UID).Get(&existingByUid)
if err != nil {
return fmt.Errorf("SQL query for existing dashboard by UID failed: %w", err)
}
}
if !dashWithIdExists && !dashWithUidExists {
return nil
}
if dashWithIdExists && dashWithUidExists && existingById.ID != existingByUid.ID {
return dashboards.ErrDashboardWithSameUIDExists
}
existing := existingById
if !dashWithIdExists && dashWithUidExists {
dash.SetID(existingByUid.ID)
dash.SetUID(existingByUid.UID)
existing = existingByUid
}
if (existing.IsFolder && !dash.IsFolder) ||
(!existing.IsFolder && dash.IsFolder) {
return dashboards.ErrDashboardTypeMismatch
}
if !dash.IsFolder && dash.FolderUID != existing.FolderUID {
isParentFolderChanged = true
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if overwrite {
dash.SetVersion(existing.Version)
} else {
return dashboards.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginID != "" && !overwrite {
return dashboards.UpdatePluginDashboardError{PluginId: existing.PluginID}
}
return nil
})
if err != nil {
return false, err
}
@@ -186,7 +262,7 @@ func (d *dashboardStore) SaveProvisionedDashboard(ctx context.Context, cmd dashb
var result *dashboards.Dashboard
var err error
err = d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
result, err = saveDashboard(sess, &cmd, d.emitEntityEvent())
result, err = d.saveDashboard(ctx, sess, &cmd, d.emitEntityEvent())
if err != nil {
return err
}
@@ -207,7 +283,7 @@ func (d *dashboardStore) SaveDashboard(ctx context.Context, cmd dashboards.SaveD
var result *dashboards.Dashboard
var err error
err = d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
result, err = saveDashboard(sess, &cmd, d.emitEntityEvent())
result, err = d.saveDashboard(ctx, sess, &cmd, d.emitEntityEvent())
if err != nil {
return err
}
@@ -324,126 +400,16 @@ func (d *dashboardStore) CountInOrg(ctx context.Context, orgID int64, isFolder b
return r.Count, nil
}
func getExistingDashboardByIDOrUIDForUpdate(sess *db.Session, dash *dashboards.Dashboard, overwrite bool) (bool, error) {
dashWithIdExists := false
isParentFolderChanged := false
var existingById dashboards.Dashboard
if dash.ID > 0 {
var err error
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.ID, dash.OrgID).Get(&existingById)
if err != nil {
return false, fmt.Errorf("SQL query for existing dashboard by ID failed: %w", err)
}
if !dashWithIdExists {
return false, dashboards.ErrDashboardNotFound
}
if dash.UID == "" {
dash.SetUID(existingById.UID)
}
}
dashWithUidExists := false
var existingByUid dashboards.Dashboard
if dash.UID != "" {
var err error
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgID, dash.UID).Get(&existingByUid)
if err != nil {
return false, fmt.Errorf("SQL query for existing dashboard by UID failed: %w", err)
}
}
if !dashWithIdExists && !dashWithUidExists {
return false, nil
}
if dashWithIdExists && dashWithUidExists && existingById.ID != existingByUid.ID {
return false, dashboards.ErrDashboardWithSameUIDExists
}
existing := existingById
if !dashWithIdExists && dashWithUidExists {
dash.SetID(existingByUid.ID)
dash.SetUID(existingByUid.UID)
existing = existingByUid
}
if (existing.IsFolder && !dash.IsFolder) ||
(!existing.IsFolder && dash.IsFolder) {
return isParentFolderChanged, dashboards.ErrDashboardTypeMismatch
}
if !dash.IsFolder && dash.FolderUID != existing.FolderUID {
isParentFolderChanged = true
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if overwrite {
dash.SetVersion(existing.Version)
} else {
return isParentFolderChanged, dashboards.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginID != "" && !overwrite {
return isParentFolderChanged, dashboards.UpdatePluginDashboardError{PluginId: existing.PluginID}
}
return isParentFolderChanged, nil
}
func saveDashboard(sess *db.Session, cmd *dashboards.SaveDashboardCommand, emitEntityEvent bool) (*dashboards.Dashboard, error) {
func (d *dashboardStore) saveDashboard(ctx context.Context, sess *db.Session, cmd *dashboards.SaveDashboardCommand, emitEntityEvent bool) (*dashboards.Dashboard, error) {
dash := cmd.GetDashboardModel()
userId := cmd.UserID
if userId == 0 {
userId = -1
isParentFolderChanged, err := d.ValidateDashboardBeforeSave(ctx, dash, cmd.Overwrite)
if err != nil {
return nil, err
}
// we don't save FolderID in kubernetes object when saving through k8s
// this block guarantees we save dashboards with folder_id and folder_uid in those cases
if !dash.IsFolder && dash.FolderUID != "" && dash.FolderID == 0 { // nolint:staticcheck
var existing dashboards.Dashboard
folderIdFound, err := sess.Where("uid=? AND org_id=?", dash.FolderUID, dash.OrgID).Get(&existing)
if err != nil {
return nil, err
}
if folderIdFound {
dash.FolderID = existing.ID // nolint:staticcheck
}
}
if dash.ID > 0 {
var existing dashboards.Dashboard
dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.ID, dash.OrgID).Get(&existing)
if err != nil {
return nil, err
}
if !dashWithIdExists {
return nil, dashboards.ErrDashboardNotFound
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if cmd.Overwrite {
dash.SetVersion(existing.Version)
} else {
return nil, dashboards.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginID != "" && !cmd.Overwrite {
return nil, dashboards.UpdatePluginDashboardError{PluginId: existing.PluginID}
}
if isParentFolderChanged {
d.log.Debug("Dashboard parent folder has changed", "dashboard", dash.UID, "newFolder", dash.FolderUID)
}
if dash.UID == "" {
@@ -452,14 +418,12 @@ func saveDashboard(sess *db.Session, cmd *dashboards.SaveDashboardCommand, emitE
parentVersion := dash.Version
var affectedRows int64
var err error
if dash.ID == 0 {
dash.SetVersion(1)
dash.Created = time.Now()
dash.CreatedBy = userId
dash.CreatedBy = dash.UpdatedBy
dash.Updated = time.Now()
dash.UpdatedBy = userId
metrics.MApiDashboardInsert.Inc()
affectedRows, err = sess.Nullable("folder_uid").Insert(dash)
} else {
@@ -471,8 +435,6 @@ func saveDashboard(sess *db.Session, cmd *dashboards.SaveDashboardCommand, emitE
dash.Updated = time.Now()
}
dash.UpdatedBy = userId
affectedRows, err = sess.MustCols("folder_id", "folder_uid").Nullable("folder_uid").ID(dash.ID).Update(dash)
}
+5
View File
@@ -117,6 +117,11 @@ var (
StatusCode: 400,
Status: "bad-request",
}
ErrQuotaReached = dashboardaccess.DashboardErr{
Reason: "Dashboard quota reached",
StatusCode: 403,
Status: "quota-reached",
}
ErrFolderNotFound = errors.New("folder not found")
ErrFolderVersionMismatch = errors.New("the folder has been changed by someone else")
@@ -608,6 +608,29 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx co
return dr.dashboardStore.GetProvisionedDataByDashboardUID(ctx, orgID, dashboardUID)
}
func (dr *DashboardServiceImpl) ValidateBasicDashboardProperties(title string, uid string, message string) error {
if title == "" {
return dashboards.ErrDashboardTitleEmpty
}
if len(title) > 5000 {
return dashboards.ErrDashboardTitleTooLong
}
// Validate message
if message != "" && len(message) > 500 {
return dashboards.ErrDashboardMessageTooLong
}
if !util.IsValidShortUID(uid) {
return dashboards.ErrDashboardInvalidUid
} else if util.IsShortUIDTooLong(uid) {
return dashboards.ErrDashboardUidTooLong
}
return nil
}
//nolint:gocyclo
func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, dto *dashboards.SaveDashboardDTO,
validateProvisionedDashboard bool) (*dashboards.SaveDashboardCommand, error) {
@@ -621,16 +644,8 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
dash.Data.Set("title", dash.Title)
dash.SetUID(strings.TrimSpace(dash.UID))
if dash.Title == "" {
return nil, dashboards.ErrDashboardTitleEmpty
}
if len(dash.Title) > 5000 {
return nil, dashboards.ErrDashboardTitleTooLong
}
if len(dto.Message) > 500 {
return nil, dashboards.ErrDashboardMessageTooLong
if err := dr.ValidateBasicDashboardProperties(dash.Title, dash.UID, dto.Message); err != nil {
return nil, err
}
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc()
@@ -643,13 +658,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
return nil, dashboards.ErrDashboardFolderNameExists
}
if !util.IsValidShortUID(dash.UID) {
return nil, dashboards.ErrDashboardInvalidUid
} else if util.IsShortUIDTooLong(dash.UID) {
return nil, dashboards.ErrDashboardUidTooLong
}
if err := validateDashboardRefreshInterval(dr.cfg.MinRefreshInterval, dash); err != nil {
if err := dr.ValidateDashboardRefreshInterval(dr.cfg.MinRefreshInterval, dash.Data.Get("refresh").MustString("")); err != nil {
return nil, err
}
@@ -910,13 +919,12 @@ func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.
return dr.dashboardStore.DeleteOrphanedProvisionedDashboards(ctx, cmd)
}
func validateDashboardRefreshInterval(minRefreshInterval string, dash *dashboards.Dashboard) error {
func (dr *DashboardServiceImpl) ValidateDashboardRefreshInterval(minRefreshInterval string, targetRefreshInterval string) error {
if minRefreshInterval == "" {
return nil
}
refresh := dash.Data.Get("refresh").MustString("")
if refresh == "" || refresh == "auto" {
if targetRefreshInterval == "" || targetRefreshInterval == "auto" {
// since no refresh is set it is a valid refresh rate
return nil
}
@@ -925,9 +933,9 @@ func validateDashboardRefreshInterval(minRefreshInterval string, dash *dashboard
if err != nil {
return fmt.Errorf("parsing min refresh interval %q failed: %w", minRefreshInterval, err)
}
d, err := gtime.ParseDuration(refresh)
d, err := gtime.ParseDuration(targetRefreshInterval)
if err != nil {
return fmt.Errorf("parsing refresh duration %q failed: %w", refresh, err)
return fmt.Errorf("parsing refresh duration %q failed: %w", targetRefreshInterval, err)
}
if d < minRefreshIntervalDur {
@@ -942,7 +950,7 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
ctx, span := tracer.Start(ctx, "dashboards.service.SaveProvisionedDashboard")
defer span.End()
if err := validateDashboardRefreshInterval(dr.cfg.MinRefreshInterval, dto.Dashboard); err != nil {
if err := dr.ValidateDashboardRefreshInterval(dr.cfg.MinRefreshInterval, dto.Dashboard.Data.Get("refresh").MustString("")); err != nil {
dr.log.Warn("Changing refresh interval for provisioned dashboard to minimum refresh interval", "dashboardUid",
dto.Dashboard.UID, "dashboardTitle", dto.Dashboard.Title, "minRefreshInterval", dr.cfg.MinRefreshInterval)
dto.Dashboard.Data.Set("refresh", dr.cfg.MinRefreshInterval)
@@ -971,7 +979,7 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
}
if dto.Dashboard.ID == 0 {
dr.setDefaultPermissions(ctx, dto, dash, true)
dr.SetDefaultPermissions(ctx, dto, dash, true)
}
return dash, nil
@@ -1002,7 +1010,7 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar
ctx, span := tracer.Start(ctx, "dashboards.service.SaveDashboard")
defer span.End()
if err := validateDashboardRefreshInterval(dr.cfg.MinRefreshInterval, dto.Dashboard); err != nil {
if err := dr.ValidateDashboardRefreshInterval(dr.cfg.MinRefreshInterval, dto.Dashboard.Data.Get("refresh").MustString("")); err != nil {
dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval",
"dashboardUid", dto.Dashboard.UID, "dashboardTitle", dto.Dashboard.Title, "minRefreshInterval",
dr.cfg.MinRefreshInterval)
@@ -1021,7 +1029,7 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar
// new dashboard created
if dto.Dashboard.ID == 0 {
dr.setDefaultPermissions(ctx, dto, dash, false)
dr.SetDefaultPermissions(ctx, dto, dash, false)
}
return dash, nil
@@ -1095,7 +1103,7 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashbo
ctx, span := tracer.Start(ctx, "dashboards.service.ImportDashboard")
defer span.End()
if err := validateDashboardRefreshInterval(dr.cfg.MinRefreshInterval, dto.Dashboard); err != nil {
if err := dr.ValidateDashboardRefreshInterval(dr.cfg.MinRefreshInterval, dto.Dashboard.Data.Get("refresh").MustString("")); err != nil {
dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval",
"dashboardUid", dto.Dashboard.UID, "dashboardTitle", dto.Dashboard.Title,
"minRefreshInterval", dr.cfg.MinRefreshInterval)
@@ -1112,7 +1120,7 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashbo
return nil, err
}
dr.setDefaultPermissions(ctx, dto, dash, false)
dr.SetDefaultPermissions(ctx, dto, dash, false)
return dash, nil
}
@@ -1178,7 +1186,7 @@ func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, que
return dr.dashboardStore.GetDashboardsByPluginID(ctx, query)
}
func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *dashboards.SaveDashboardDTO, dash *dashboards.Dashboard, provisioned bool) {
func (dr *DashboardServiceImpl) SetDefaultPermissions(ctx context.Context, dto *dashboards.SaveDashboardDTO, dash *dashboards.Dashboard, provisioned bool) {
ctx, span := tracer.Start(ctx, "dashboards.service.setDefaultPermissions")
defer span.End()
@@ -1765,9 +1773,14 @@ func (dr *DashboardServiceImpl) saveProvisionedDashboardThroughK8s(ctx context.C
meta.SetManagerProperties(m)
meta.SetSourceProperties(s)
// Update will create if not exists (upsert!)
out, err := dr.k8sclient.Update(ctx, obj, cmd.OrgID)
if err != nil {
if err != nil && apierrors.IsNotFound(err) {
// Create if it doesn't already exist.
out, err = dr.k8sclient.Create(ctx, obj, cmd.OrgID)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
@@ -1782,9 +1795,14 @@ func (dr *DashboardServiceImpl) saveDashboardThroughK8s(ctx context.Context, cmd
dashboard.SetPluginIDMeta(obj, cmd.PluginID)
// Update will create if not exists (upsert!)
out, err := dr.k8sclient.Update(ctx, obj, orgID)
if err != nil {
if err != nil && apierrors.IsNotFound(err) {
// Create if it doesn't already exist.
out, err = dr.k8sclient.Create(ctx, obj, orgID)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
@@ -2161,7 +2161,7 @@ func TestCountDashboardsInOrg(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("CountInOrg", mock.Anything, mock.Anything, false).Return(nil, nil).Once()
fakeStore.On("CountInOrg", mock.Anything, mock.Anything, false).Return(int64(1), nil).Once()
_, err := service.CountDashboardsInOrg(context.Background(), 1)
require.NoError(t, err)
fakeStore.AssertExpectations(t)
+7 -10
View File
@@ -1,13 +1,12 @@
// Code generated by mockery v2.52.2. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package dashboards
import (
context "context"
mock "github.com/stretchr/testify/mock"
quota "github.com/grafana/grafana/pkg/services/quota"
mock "github.com/stretchr/testify/mock"
)
// FakeDashboardStore is an autogenerated mock type for the Store type
@@ -91,12 +90,12 @@ func (_m *FakeDashboardStore) CountDashboardsInFolders(ctx context.Context, requ
return r0, r1
}
// CountInOrg provides a mock function with given fields: ctx, orgID
// CountInOrg provides a mock function with given fields: ctx, orgID, isFolder
func (_m *FakeDashboardStore) CountInOrg(ctx context.Context, orgID int64, isFolder bool) (int64, error) {
ret := _m.Called(ctx, orgID, isFolder)
if len(ret) == 0 {
panic("no return value specified for Count")
panic("no return value specified for CountInOrg")
}
var r0 int64
@@ -107,9 +106,7 @@ func (_m *FakeDashboardStore) CountInOrg(ctx context.Context, orgID int64, isFol
if rf, ok := ret.Get(0).(func(context.Context, int64, bool) int64); ok {
r0 = rf(ctx, orgID, isFolder)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(int64)
}
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(context.Context, int64, bool) error); ok {
@@ -433,7 +430,7 @@ func (_m *FakeDashboardStore) GetDashboardsByPluginID(ctx context.Context, query
return r0, r1
}
// GetOrphanedProvisionedDashboards provides a mock function with given fields: ctx, notIn
// GetOrphanedProvisionedDashboards provides a mock function with given fields: ctx, notIn, orgID
func (_m *FakeDashboardStore) GetOrphanedProvisionedDashboards(ctx context.Context, notIn []string, orgID int64) ([]*Dashboard, error) {
ret := _m.Called(ctx, notIn, orgID)
@@ -493,7 +490,7 @@ func (_m *FakeDashboardStore) GetProvisionedDashboardData(ctx context.Context, n
return r0, r1
}
// GetProvisionedDashboardsByName provides a mock function with given fields: ctx, name
// GetProvisionedDashboardsByName provides a mock function with given fields: ctx, name, orgID
func (_m *FakeDashboardStore) GetProvisionedDashboardsByName(ctx context.Context, name string, orgID int64) ([]*Dashboard, error) {
ret := _m.Called(ctx, name, orgID)