diff --git a/pkg/services/dashboardsnapshots/service.go b/pkg/services/dashboardsnapshots/service.go index 3fcf4d42d45..8299fa19ce9 100644 --- a/pkg/services/dashboardsnapshots/service.go +++ b/pkg/services/dashboardsnapshots/service.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "net/http" "time" @@ -15,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -26,6 +28,7 @@ type Service interface { DeleteExpiredSnapshots(context.Context, *DeleteExpiredSnapshotsCommand) error GetDashboardSnapshot(context.Context, *GetDashboardSnapshotQuery) (*DashboardSnapshot, error) SearchDashboardSnapshots(context.Context, *GetDashboardSnapshotsQuery) (DashboardSnapshotsList, error) + ValidateDashboardExists(context.Context, int64, string) error } var client = &http.Client{ @@ -39,6 +42,18 @@ func CreateDashboardSnapshot(c *contextmodel.ReqContext, cfg dashboardsnapshot.S return } + uid := cmd.DashboardCreateCommand.Dashboard.GetNestedString("uid") + + err := svc.ValidateDashboardExists(c.Req.Context(), c.SignedInUser.GetOrgID(), uid) + if err != nil { + if errors.Is(err, dashboards.ErrDashboardNotFound) { + c.JsonApiErr(http.StatusBadRequest, "Dashboard not found", err) + return + } + c.JsonApiErr(http.StatusInternalServerError, "Failed to get dashboard", err) + return + } + if cmd.DashboardCreateCommand.Name == "" { cmd.DashboardCreateCommand.Name = "Unnamed snapshot" } diff --git a/pkg/services/dashboardsnapshots/service/service.go b/pkg/services/dashboardsnapshots/service/service.go index c26f00e7d23..2d072355c61 100644 --- a/pkg/services/dashboardsnapshots/service/service.go +++ b/pkg/services/dashboardsnapshots/service/service.go @@ -4,27 +4,35 @@ import ( "context" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/secrets" ) type ServiceImpl struct { - store dashboardsnapshots.Store - secretsService secrets.Service + store dashboardsnapshots.Store + secretsService secrets.Service + dashboardService dashboards.DashboardService } // ServiceImpl implements the dashboardsnapshots Service interface var _ dashboardsnapshots.Service = (*ServiceImpl)(nil) -func ProvideService(store dashboardsnapshots.Store, secretsService secrets.Service) *ServiceImpl { +func ProvideService(store dashboardsnapshots.Store, secretsService secrets.Service, dashboardService dashboards.DashboardService) *ServiceImpl { s := &ServiceImpl{ - store: store, - secretsService: secretsService, + store: store, + secretsService: secretsService, + dashboardService: dashboardService, } return s } +func (s *ServiceImpl) ValidateDashboardExists(ctx context.Context, orgId int64, dashboardUid string) error { + _, err := s.dashboardService.GetDashboard(ctx, &dashboards.GetDashboardQuery{UID: dashboardUid, OrgID: orgId}) + return err +} + func (s *ServiceImpl) CreateDashboardSnapshot(ctx context.Context, cmd *dashboardsnapshots.CreateDashboardSnapshotCommand) (*dashboardsnapshots.DashboardSnapshot, error) { marshalledData, err := cmd.Dashboard.MarshalJSON() if err != nil { diff --git a/pkg/services/dashboardsnapshots/service/service_test.go b/pkg/services/dashboardsnapshots/service/service_test.go index f71de4d1768..c2cdf73efad 100644 --- a/pkg/services/dashboardsnapshots/service/service_test.go +++ b/pkg/services/dashboardsnapshots/service/service_test.go @@ -4,16 +4,26 @@ import ( "context" "encoding/json" "testing" + "time" "github.com/stretchr/testify/require" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1" "github.com/grafana/grafana/pkg/infra/db" + acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/dashboards" + dashdb "github.com/grafana/grafana/pkg/services/dashboards/database" + dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" dashsnapdb "github.com/grafana/grafana/pkg/services/dashboardsnapshots/database" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/folder/folderimpl" + "github.com/grafana/grafana/pkg/services/folder/foldertest" + "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/secrets/database" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" + "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/testsuite" ) @@ -26,8 +36,9 @@ func TestDashboardSnapshotsService(t *testing.T) { sqlStore := db.InitTestDB(t) cfg := setting.NewCfg() dsStore := dashsnapdb.ProvideStore(sqlStore, cfg) + fakeDashboardService := &dashboards.FakeDashboardService{} secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore)) - s := ProvideService(dsStore, secretsService) + s := ProvideService(dsStore, secretsService, fakeDashboardService) origSecret := cfg.SecretKey cfg.SecretKey = "dashboard_snapshot_service_test" @@ -79,3 +90,45 @@ func TestDashboardSnapshotsService(t *testing.T) { require.Equal(t, rawDashboard, decrypted) }) } + +func TestValidateDashboardExists(t *testing.T) { + sqlStore := db.InitTestDB(t) + cfg := setting.NewCfg() + dsStore := dashsnapdb.ProvideStore(sqlStore, cfg) + secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore)) + dashboardStore, err := dashdb.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil)) + require.NoError(t, err) + dashSvc, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashboardStore, folderimpl.ProvideDashboardFolderStore(sqlStore), nil, nil, nil, acmock.New(), foldertest.NewFakeService(), nil) + require.NoError(t, err) + s := ProvideService(dsStore, secretsService, dashSvc) + ctx := context.Background() + + t.Run("returns false when dashboard does not exist", func(t *testing.T) { + err := s.ValidateDashboardExists(ctx, 1, "test") + require.Error(t, err) + require.Equal(t, dashboards.ErrDashboardNotFound, err) + }) + + t.Run("returns true when dashboard exists", func(t *testing.T) { + err := createDashboard(sqlStore) + require.NoError(t, err) + + err = s.ValidateDashboardExists(ctx, 1, "test") + require.NoError(t, err) + }) +} + +func createDashboard(store db.DB) error { + return store.WithDbSession(context.Background(), func(sess *db.Session) error { + dashboard := &dashboards.Dashboard{ + ID: 1, + UID: "test", + OrgID: 1, + Created: time.Now(), + Updated: time.Now(), + } + + _, err := sess.Insert(dashboard) + return err + }) +} diff --git a/pkg/services/dashboardsnapshots/service_mock.go b/pkg/services/dashboardsnapshots/service_mock.go index 8773fbd3a68..4d3ef270349 100644 --- a/pkg/services/dashboardsnapshots/service_mock.go +++ b/pkg/services/dashboardsnapshots/service_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.0. DO NOT EDIT. +// Code generated by mockery v2.34.0. DO NOT EDIT. package dashboardsnapshots @@ -119,6 +119,20 @@ func (_m *MockService) SearchDashboardSnapshots(_a0 context.Context, _a1 *GetDas return r0, r1 } +// ValidateDashboardExists provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockService) ValidateDashboardExists(_a0 context.Context, _a1 int64, _a2 string) error { + ret := _m.Called(_a0, _a1, _a2) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockService(t interface {