diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index cbb17fa5945..f77dd9b2af0 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -5,6 +5,7 @@ import ( alertmodels "github.com/grafana/grafana/pkg/services/alerting/models" "github.com/grafana/grafana/pkg/services/auth/identity" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/search/model" ) @@ -41,7 +42,7 @@ type DashboardProvisioningService interface { GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error) - SaveFolderForProvisionedDashboards(context.Context, *SaveDashboardDTO) (*Dashboard, error) + SaveFolderForProvisionedDashboards(context.Context, *folder.CreateFolderCommand) (*folder.Folder, error) SaveProvisionedDashboard(ctx context.Context, dto *SaveDashboardDTO, provisioning *DashboardProvisioning) (*Dashboard, error) UnprovisionDashboard(ctx context.Context, dashboardID int64) error } diff --git a/pkg/services/dashboards/dashboard_provisioning_mock.go b/pkg/services/dashboards/dashboard_provisioning_mock.go index 7b6a1119924..9931ad15bda 100644 --- a/pkg/services/dashboards/dashboard_provisioning_mock.go +++ b/pkg/services/dashboards/dashboard_provisioning_mock.go @@ -1,10 +1,11 @@ -// Code generated by mockery v2.28.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package dashboards import ( context "context" + folder "github.com/grafana/grafana/pkg/services/folder" mock "github.com/stretchr/testify/mock" ) @@ -17,6 +18,10 @@ type FakeDashboardProvisioning struct { func (_m *FakeDashboardProvisioning) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *DeleteOrphanedProvisionedDashboardsCommand) error { ret := _m.Called(ctx, cmd) + if len(ret) == 0 { + panic("no return value specified for DeleteOrphanedProvisionedDashboards") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *DeleteOrphanedProvisionedDashboardsCommand) error); ok { r0 = rf(ctx, cmd) @@ -31,6 +36,10 @@ func (_m *FakeDashboardProvisioning) DeleteOrphanedProvisionedDashboards(ctx con func (_m *FakeDashboardProvisioning) DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error { ret := _m.Called(ctx, dashboardID, orgID) + if len(ret) == 0 { + panic("no return value specified for DeleteProvisionedDashboard") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, dashboardID, orgID) @@ -45,6 +54,10 @@ func (_m *FakeDashboardProvisioning) DeleteProvisionedDashboard(ctx context.Cont func (_m *FakeDashboardProvisioning) GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) { ret := _m.Called(ctx, name) + if len(ret) == 0 { + panic("no return value specified for GetProvisionedDashboardData") + } + var r0 []*DashboardProvisioning var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*DashboardProvisioning, error)); ok { @@ -71,6 +84,10 @@ func (_m *FakeDashboardProvisioning) GetProvisionedDashboardData(ctx context.Con func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) { ret := _m.Called(ctx, dashboardID) + if len(ret) == 0 { + panic("no return value specified for GetProvisionedDashboardDataByDashboardID") + } + var r0 *DashboardProvisioning var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*DashboardProvisioning, error)); ok { @@ -97,6 +114,10 @@ func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardID(ct func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error) { ret := _m.Called(ctx, orgID, dashboardUID) + if len(ret) == 0 { + panic("no return value specified for GetProvisionedDashboardDataByDashboardUID") + } + var r0 *DashboardProvisioning var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*DashboardProvisioning, error)); ok { @@ -120,23 +141,27 @@ func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardUID(c } // SaveFolderForProvisionedDashboards provides a mock function with given fields: _a0, _a1 -func (_m *FakeDashboardProvisioning) SaveFolderForProvisionedDashboards(_a0 context.Context, _a1 *SaveDashboardDTO) (*Dashboard, error) { +func (_m *FakeDashboardProvisioning) SaveFolderForProvisionedDashboards(_a0 context.Context, _a1 *folder.CreateFolderCommand) (*folder.Folder, error) { ret := _m.Called(_a0, _a1) - var r0 *Dashboard + if len(ret) == 0 { + panic("no return value specified for SaveFolderForProvisionedDashboards") + } + + var r0 *folder.Folder var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO) (*Dashboard, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, *folder.CreateFolderCommand) (*folder.Folder, error)); ok { return rf(_a0, _a1) } - if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO) *Dashboard); ok { + if rf, ok := ret.Get(0).(func(context.Context, *folder.CreateFolderCommand) *folder.Folder); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*Dashboard) + r0 = ret.Get(0).(*folder.Folder) } } - if rf, ok := ret.Get(1).(func(context.Context, *SaveDashboardDTO) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *folder.CreateFolderCommand) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -149,6 +174,10 @@ func (_m *FakeDashboardProvisioning) SaveFolderForProvisionedDashboards(_a0 cont func (_m *FakeDashboardProvisioning) SaveProvisionedDashboard(ctx context.Context, dto *SaveDashboardDTO, provisioning *DashboardProvisioning) (*Dashboard, error) { ret := _m.Called(ctx, dto, provisioning) + if len(ret) == 0 { + panic("no return value specified for SaveProvisionedDashboard") + } + var r0 *Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, *DashboardProvisioning) (*Dashboard, error)); ok { @@ -175,6 +204,10 @@ func (_m *FakeDashboardProvisioning) SaveProvisionedDashboard(ctx context.Contex func (_m *FakeDashboardProvisioning) UnprovisionDashboard(ctx context.Context, dashboardID int64) error { ret := _m.Called(ctx, dashboardID) + if len(ret) == 0 { + panic("no return value specified for UnprovisionDashboard") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, dashboardID) @@ -185,13 +218,12 @@ func (_m *FakeDashboardProvisioning) UnprovisionDashboard(ctx context.Context, d return r0 } -type mockConstructorTestingTNewFakeDashboardProvisioning interface { +// NewFakeDashboardProvisioning creates a new instance of FakeDashboardProvisioning. 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 NewFakeDashboardProvisioning(t interface { mock.TestingT Cleanup(func()) -} - -// NewFakeDashboardProvisioning creates a new instance of FakeDashboardProvisioning. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewFakeDashboardProvisioning(t mockConstructorTestingTNewFakeDashboardProvisioning) *FakeDashboardProvisioning { +}) *FakeDashboardProvisioning { mock := &FakeDashboardProvisioning{} mock.Mock.Test(t) diff --git a/pkg/services/dashboards/dashboard_service_mock.go b/pkg/services/dashboards/dashboard_service_mock.go index 239468e14f1..828842e2a37 100644 --- a/pkg/services/dashboards/dashboard_service_mock.go +++ b/pkg/services/dashboards/dashboard_service_mock.go @@ -1,11 +1,13 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + package dashboards import ( context "context" + identity "github.com/grafana/grafana/pkg/services/auth/identity" mock "github.com/stretchr/testify/mock" - "github.com/grafana/grafana/pkg/services/auth/identity" model "github.com/grafana/grafana/pkg/services/search/model" ) @@ -18,6 +20,10 @@ type FakeDashboardService struct { func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*SaveDashboardCommand, error) { ret := _m.Called(ctx, dto, shouldValidateAlerts, validateProvisionedDashboard) + if len(ret) == 0 { + panic("no return value specified for BuildSaveDashboardCommand") + } + var r0 *SaveDashboardCommand var r1 error if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, bool, bool) (*SaveDashboardCommand, error)); ok { @@ -40,23 +46,27 @@ func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, d return r0, r1 } -// CountInFolder provides a mock function with given fields: ctx, orgID, folderUID, _a3 -func (_m *FakeDashboardService) CountInFolder(ctx context.Context, orgID int64, folderUID string, _a3 identity.Requester) (int64, error) { - ret := _m.Called(ctx, orgID, folderUID, _a3) +// CountInFolder provides a mock function with given fields: ctx, orgID, folderUID, user +func (_m *FakeDashboardService) CountInFolder(ctx context.Context, orgID int64, folderUID string, user identity.Requester) (int64, error) { + ret := _m.Called(ctx, orgID, folderUID, user) + + if len(ret) == 0 { + panic("no return value specified for CountInFolder") + } var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string, identity.Requester) (int64, error)); ok { - return rf(ctx, orgID, folderUID, _a3) + return rf(ctx, orgID, folderUID, user) } if rf, ok := ret.Get(0).(func(context.Context, int64, string, identity.Requester) int64); ok { - r0 = rf(ctx, orgID, folderUID, _a3) + r0 = rf(ctx, orgID, folderUID, user) } else { r0 = ret.Get(0).(int64) } if rf, ok := ret.Get(1).(func(context.Context, int64, string, identity.Requester) error); ok { - r1 = rf(ctx, orgID, folderUID, _a3) + r1 = rf(ctx, orgID, folderUID, user) } else { r1 = ret.Error(1) } @@ -68,6 +78,10 @@ func (_m *FakeDashboardService) CountInFolder(ctx context.Context, orgID int64, func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error { ret := _m.Called(ctx, dashboardId, orgId) + if len(ret) == 0 { + panic("no return value specified for DeleteDashboard") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { r0 = rf(ctx, dashboardId, orgId) @@ -82,6 +96,10 @@ func (_m *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for FindDashboards") + } + var r0 []DashboardSearchProjection var r1 error if rf, ok := ret.Get(0).(func(context.Context, *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)); ok { @@ -108,6 +126,10 @@ func (_m *FakeDashboardService) FindDashboards(ctx context.Context, query *FindP func (_m *FakeDashboardService) GetDashboard(ctx context.Context, query *GetDashboardQuery) (*Dashboard, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetDashboard") + } + var r0 *Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardQuery) (*Dashboard, error)); ok { @@ -134,6 +156,10 @@ func (_m *FakeDashboardService) GetDashboard(ctx context.Context, query *GetDash func (_m *FakeDashboardService) GetDashboardTags(ctx context.Context, query *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetDashboardTags") + } + var r0 []*DashboardTagCloudItem var r1 error if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error)); ok { @@ -160,6 +186,10 @@ func (_m *FakeDashboardService) GetDashboardTags(ctx context.Context, query *Get func (_m *FakeDashboardService) GetDashboardUIDByID(ctx context.Context, query *GetDashboardRefByIDQuery) (*DashboardRef, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetDashboardUIDByID") + } + var r0 *DashboardRef var r1 error if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardRefByIDQuery) (*DashboardRef, error)); ok { @@ -186,6 +216,10 @@ func (_m *FakeDashboardService) GetDashboardUIDByID(ctx context.Context, query * func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *GetDashboardsQuery) ([]*Dashboard, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetDashboards") + } + var r0 []*Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardsQuery) ([]*Dashboard, error)); ok { @@ -208,10 +242,44 @@ func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *GetDas return r0, r1 } +// GetDashboardsSharedWithUser provides a mock function with given fields: ctx, user +func (_m *FakeDashboardService) GetDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*Dashboard, error) { + ret := _m.Called(ctx, user) + + if len(ret) == 0 { + panic("no return value specified for GetDashboardsSharedWithUser") + } + + var r0 []*Dashboard + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) ([]*Dashboard, error)); ok { + return rf(ctx, user) + } + if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) []*Dashboard); ok { + r0 = rf(ctx, user) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*Dashboard) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, identity.Requester) error); ok { + r1 = rf(ctx, user) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ImportDashboard provides a mock function with given fields: ctx, dto func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*Dashboard, error) { ret := _m.Called(ctx, dto) + if len(ret) == 0 { + panic("no return value specified for ImportDashboard") + } + var r0 *Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO) (*Dashboard, error)); ok { @@ -238,6 +306,10 @@ func (_m *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDa func (_m *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*Dashboard, error) { ret := _m.Called(ctx, dto, allowUiUpdate) + if len(ret) == 0 { + panic("no return value specified for SaveDashboard") + } + var r0 *Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, bool) (*Dashboard, error)); ok { @@ -264,6 +336,10 @@ func (_m *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDash func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) (model.HitList, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for SearchDashboards") + } + var r0 model.HitList var r1 error if rf, ok := ret.Get(0).(func(context.Context, *FindPersistedDashboardsQuery) (model.HitList, error)); ok { @@ -286,38 +362,12 @@ func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *Fin return r0, r1 } -func (_m *FakeDashboardService) GetDashboardsSharedWithUser(ctx context.Context, user identity.Requester) ([]*Dashboard, error) { - ret := _m.Called(ctx, user) - - var r0 []*Dashboard - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) ([]*Dashboard, error)); ok { - return rf(ctx, user) - } - if rf, ok := ret.Get(0).(func(context.Context, identity.Requester) []*Dashboard); ok { - r0 = rf(ctx, user) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*Dashboard) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, identity.Requester) error); ok { - r1 = rf(ctx, user) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -type mockConstructorTestingTNewFakeDashboardService interface { +// NewFakeDashboardService creates a new instance of FakeDashboardService. 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 NewFakeDashboardService(t interface { mock.TestingT Cleanup(func()) -} - -// NewFakeDashboardService creates a new instance of FakeDashboardService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewFakeDashboardService(t mockConstructorTestingTNewFakeDashboardService) *FakeDashboardService { +}) *FakeDashboardService { mock := &FakeDashboardService{} mock.Mock.Test(t) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 49ee617d39a..89c9fd2d8a9 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -349,42 +349,17 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt return dash, nil } -func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.Context, dto *dashboards.SaveDashboardDTO) (*dashboards.Dashboard, error) { - dto.User = accesscontrol.BackgroundUser("dashboard_provisioning", dto.OrgID, org.RoleAdmin, provisionerPermissions) - cmd, err := dr.BuildSaveDashboardCommand(ctx, dto, false, false) +func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.Context, dto *folder.CreateFolderCommand) (*folder.Folder, error) { + dto.SignedInUser = accesscontrol.BackgroundUser("dashboard_provisioning", dto.OrgID, org.RoleAdmin, provisionerPermissions) + + f, err := dr.folderService.Create(ctx, dto) if err != nil { + dr.log.Error("failed to create folder for provisioned dashboards", "folder", dto.Title, "org", dto.OrgID, "err", err) return nil, err } - dash, err := dr.dashboardStore.SaveDashboard(ctx, *cmd) - if err != nil { - return nil, err - } - - dashAlertInfo := alerting.DashAlertInfo{ - User: dto.User, - Dash: dash, - OrgID: dto.OrgID, - } - - // extract/save legacy alerts only if legacy alerting is enabled - if setting.IsLegacyAlertingEnabled() { - alerts, err := dr.dashAlertExtractor.GetAlerts(ctx, dashAlertInfo) - if err != nil { - return nil, err - } - - err = dr.dashboardStore.SaveAlerts(ctx, dash.ID, alerts) - if err != nil { - return nil, err - } - } - - if dto.Dashboard.ID == 0 { - dr.setDefaultPermissions(ctx, dto, dash, true) - } - - return dash, nil + dr.setDefaultFolderPermissions(ctx, dto, f, true) + return f, nil } func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO, @@ -532,6 +507,35 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto * } } +func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, cmd *folder.CreateFolderCommand, f *folder.Folder, provisioned bool) { + inFolder := f.ParentUID != "" + var permissions []accesscontrol.SetResourcePermissionCommand + + if !provisioned { + namespaceID, userIDstr := cmd.SignedInUser.GetNamespacedID() + userID, err := identity.IntIdentifier(namespaceID, userIDstr) + + if err != nil { + dr.log.Error("Could not make user admin", "folder", cmd.Title, "namespaceID", namespaceID, "userID", userID, "error", err) + } else if namespaceID == identity.NamespaceUser && userID > 0 { + permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ + UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), + }) + } + } + + if !inFolder { + permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ + {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, + {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, + }...) + } + + if _, err := dr.folderPermissions.SetPermissions(ctx, cmd.OrgID, f.UID, permissions...); err != nil { + dr.log.Error("Could not set default folder permissions", "folder", f.Title, "error", err) + } +} + func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) { return dr.dashboardStore.GetDashboard(ctx, query) } diff --git a/pkg/services/dashboards/store_mock.go b/pkg/services/dashboards/store_mock.go index d4a696d6ae0..71422fd1967 100644 --- a/pkg/services/dashboards/store_mock.go +++ b/pkg/services/dashboards/store_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.28.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package dashboards @@ -20,6 +20,10 @@ type FakeDashboardStore struct { func (_m *FakeDashboardStore) Count(_a0 context.Context, _a1 *quota.ScopeParameters) (*quota.Map, error) { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for Count") + } + var r0 *quota.Map var r1 error if rf, ok := ret.Get(0).(func(context.Context, *quota.ScopeParameters) (*quota.Map, error)); ok { @@ -46,6 +50,10 @@ func (_m *FakeDashboardStore) Count(_a0 context.Context, _a1 *quota.ScopeParamet func (_m *FakeDashboardStore) CountDashboardsInFolder(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error) { ret := _m.Called(ctx, request) + if len(ret) == 0 { + panic("no return value specified for CountDashboardsInFolder") + } + var r0 int64 var r1 error if rf, ok := ret.Get(0).(func(context.Context, *CountDashboardsInFolderRequest) (int64, error)); ok { @@ -70,6 +78,10 @@ func (_m *FakeDashboardStore) CountDashboardsInFolder(ctx context.Context, reque func (_m *FakeDashboardStore) DeleteDashboard(ctx context.Context, cmd *DeleteDashboardCommand) error { ret := _m.Called(ctx, cmd) + if len(ret) == 0 { + panic("no return value specified for DeleteDashboard") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *DeleteDashboardCommand) error); ok { r0 = rf(ctx, cmd) @@ -84,6 +96,10 @@ func (_m *FakeDashboardStore) DeleteDashboard(ctx context.Context, cmd *DeleteDa func (_m *FakeDashboardStore) DeleteDashboardsInFolder(ctx context.Context, request *DeleteDashboardsInFolderRequest) error { ret := _m.Called(ctx, request) + if len(ret) == 0 { + panic("no return value specified for DeleteDashboardsInFolder") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *DeleteDashboardsInFolderRequest) error); ok { r0 = rf(ctx, request) @@ -98,6 +114,10 @@ func (_m *FakeDashboardStore) DeleteDashboardsInFolder(ctx context.Context, requ func (_m *FakeDashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *DeleteOrphanedProvisionedDashboardsCommand) error { ret := _m.Called(ctx, cmd) + if len(ret) == 0 { + panic("no return value specified for DeleteOrphanedProvisionedDashboards") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *DeleteOrphanedProvisionedDashboardsCommand) error); ok { r0 = rf(ctx, cmd) @@ -112,6 +132,10 @@ func (_m *FakeDashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Co func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for FindDashboards") + } + var r0 []DashboardSearchProjection var r1 error if rf, ok := ret.Get(0).(func(context.Context, *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error)); ok { @@ -138,6 +162,10 @@ func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *FindPer func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *GetDashboardQuery) (*Dashboard, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetDashboard") + } + var r0 *Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardQuery) (*Dashboard, error)); ok { @@ -164,6 +192,10 @@ func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *GetDashbo func (_m *FakeDashboardStore) GetDashboardTags(ctx context.Context, query *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetDashboardTags") + } + var r0 []*DashboardTagCloudItem var r1 error if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardTagsQuery) ([]*DashboardTagCloudItem, error)); ok { @@ -190,6 +222,10 @@ func (_m *FakeDashboardStore) GetDashboardTags(ctx context.Context, query *GetDa func (_m *FakeDashboardStore) GetDashboardUIDByID(ctx context.Context, query *GetDashboardRefByIDQuery) (*DashboardRef, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetDashboardUIDByID") + } + var r0 *DashboardRef var r1 error if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardRefByIDQuery) (*DashboardRef, error)); ok { @@ -216,6 +252,10 @@ func (_m *FakeDashboardStore) GetDashboardUIDByID(ctx context.Context, query *Ge func (_m *FakeDashboardStore) GetDashboards(ctx context.Context, query *GetDashboardsQuery) ([]*Dashboard, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetDashboards") + } + var r0 []*Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardsQuery) ([]*Dashboard, error)); ok { @@ -242,6 +282,10 @@ func (_m *FakeDashboardStore) GetDashboards(ctx context.Context, query *GetDashb func (_m *FakeDashboardStore) GetDashboardsByPluginID(ctx context.Context, query *GetDashboardsByPluginIDQuery) ([]*Dashboard, error) { ret := _m.Called(ctx, query) + if len(ret) == 0 { + panic("no return value specified for GetDashboardsByPluginID") + } + var r0 []*Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, *GetDashboardsByPluginIDQuery) ([]*Dashboard, error)); ok { @@ -268,6 +312,10 @@ func (_m *FakeDashboardStore) GetDashboardsByPluginID(ctx context.Context, query func (_m *FakeDashboardStore) GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) { ret := _m.Called(ctx, name) + if len(ret) == 0 { + panic("no return value specified for GetProvisionedDashboardData") + } + var r0 []*DashboardProvisioning var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*DashboardProvisioning, error)); ok { @@ -294,6 +342,10 @@ func (_m *FakeDashboardStore) GetProvisionedDashboardData(ctx context.Context, n func (_m *FakeDashboardStore) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) { ret := _m.Called(ctx, dashboardID) + if len(ret) == 0 { + panic("no return value specified for GetProvisionedDataByDashboardID") + } + var r0 *DashboardProvisioning var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*DashboardProvisioning, error)); ok { @@ -320,6 +372,10 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardID(ctx context.Contex func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error) { ret := _m.Called(ctx, orgID, dashboardUID) + if len(ret) == 0 { + panic("no return value specified for GetProvisionedDataByDashboardUID") + } + var r0 *DashboardProvisioning var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64, string) (*DashboardProvisioning, error)); ok { @@ -346,6 +402,10 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(ctx context.Conte func (_m *FakeDashboardStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error { ret := _m.Called(ctx, dashID, alerts) + if len(ret) == 0 { + panic("no return value specified for SaveAlerts") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Alert) error); ok { r0 = rf(ctx, dashID, alerts) @@ -360,6 +420,10 @@ func (_m *FakeDashboardStore) SaveAlerts(ctx context.Context, dashID int64, aler func (_m *FakeDashboardStore) SaveDashboard(ctx context.Context, cmd SaveDashboardCommand) (*Dashboard, error) { ret := _m.Called(ctx, cmd) + if len(ret) == 0 { + panic("no return value specified for SaveDashboard") + } + var r0 *Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, SaveDashboardCommand) (*Dashboard, error)); ok { @@ -386,6 +450,10 @@ func (_m *FakeDashboardStore) SaveDashboard(ctx context.Context, cmd SaveDashboa func (_m *FakeDashboardStore) SaveProvisionedDashboard(ctx context.Context, cmd SaveDashboardCommand, provisioning *DashboardProvisioning) (*Dashboard, error) { ret := _m.Called(ctx, cmd, provisioning) + if len(ret) == 0 { + panic("no return value specified for SaveProvisionedDashboard") + } + var r0 *Dashboard var r1 error if rf, ok := ret.Get(0).(func(context.Context, SaveDashboardCommand, *DashboardProvisioning) (*Dashboard, error)); ok { @@ -412,6 +480,10 @@ func (_m *FakeDashboardStore) SaveProvisionedDashboard(ctx context.Context, cmd func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for UnprovisionDashboard") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { r0 = rf(ctx, id) @@ -426,6 +498,10 @@ func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64 func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(ctx context.Context, dashboard *Dashboard, overwrite bool) (bool, error) { ret := _m.Called(ctx, dashboard, overwrite) + if len(ret) == 0 { + panic("no return value specified for ValidateDashboardBeforeSave") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, *Dashboard, bool) (bool, error)); ok { @@ -446,13 +522,12 @@ func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(ctx context.Context, d return r0, r1 } -type mockConstructorTestingTNewFakeDashboardStore interface { +// NewFakeDashboardStore creates a new instance of FakeDashboardStore. 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 NewFakeDashboardStore(t interface { mock.TestingT Cleanup(func()) -} - -// NewFakeDashboardStore creates a new instance of FakeDashboardStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewFakeDashboardStore(t mockConstructorTestingTNewFakeDashboardStore) *FakeDashboardStore { +}) *FakeDashboardStore { mock := &FakeDashboardStore{} mock.Mock.Test(t) diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index 75aa5e8f794..af2ff915ea3 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/setting" @@ -177,10 +178,12 @@ func (ss *sqlStore) Get(ctx context.Context, q folder.GetFolderQuery) (*folder.F return folder.ErrDatabaseError.Errorf("failed to get folder: %w", err) } if !exists { - return folder.ErrFolderNotFound.Errorf("folder not found") + // embed dashboards.ErrFolderNotFound + return folder.ErrFolderNotFound.Errorf("%w", dashboards.ErrFolderNotFound) } return nil }) + return foldr.WithURL(), err } diff --git a/pkg/services/provisioning/alerting/rules_provisioner.go b/pkg/services/provisioning/alerting/rules_provisioner.go index 5a84d0bd13b..a95835da072 100644 --- a/pkg/services/provisioning/alerting/rules_provisioner.go +++ b/pkg/services/provisioning/alerting/rules_provisioner.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" alert_models "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/util" @@ -107,13 +108,12 @@ func (prov *defaultAlertRuleProvisioner) getOrCreateFolderUID( // dashboard folder not found. create one. if errors.Is(err, dashboards.ErrDashboardNotFound) { - dash := &dashboards.SaveDashboardDTO{} - dash.Dashboard = dashboards.NewDashboardFolder(folderName) - dash.Dashboard.IsFolder = true - dash.Overwrite = true - dash.OrgID = orgID - dash.Dashboard.SetUID(util.GenerateShortUID()) - dbDash, err := prov.dashboardProvService.SaveFolderForProvisionedDashboards(ctx, dash) + createCmd := &folder.CreateFolderCommand{ + OrgID: orgID, + UID: util.GenerateShortUID(), + Title: folderName, + } + dbDash, err := prov.dashboardProvService.SaveFolderForProvisionedDashboards(ctx, createCmd) if err != nil { return "", err } diff --git a/pkg/services/provisioning/dashboards/dashboard.go b/pkg/services/provisioning/dashboards/dashboard.go index b9c6c69095b..0a689d2e295 100644 --- a/pkg/services/provisioning/dashboards/dashboard.go +++ b/pkg/services/provisioning/dashboards/dashboard.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/provisioning/utils" ) @@ -23,7 +24,7 @@ type DashboardProvisioner interface { } // DashboardProvisionerFactory creates DashboardProvisioners based on input -type DashboardProvisionerFactory func(context.Context, string, dashboards.DashboardProvisioningService, org.Service, utils.DashboardStore) (DashboardProvisioner, error) +type DashboardProvisionerFactory func(context.Context, string, dashboards.DashboardProvisioningService, org.Service, utils.DashboardStore, folder.Service) (DashboardProvisioner, error) // Provisioner is responsible for syncing dashboard from disk to Grafana's database. type Provisioner struct { @@ -39,7 +40,7 @@ func (provider *Provisioner) HasDashboardSources() bool { } // New returns a new DashboardProvisioner -func New(ctx context.Context, configDirectory string, provisioner dashboards.DashboardProvisioningService, orgService org.Service, dashboardStore utils.DashboardStore) (DashboardProvisioner, error) { +func New(ctx context.Context, configDirectory string, provisioner dashboards.DashboardProvisioningService, orgService org.Service, dashboardStore utils.DashboardStore, folderService folder.Service) (DashboardProvisioner, error) { logger := log.New("provisioning.dashboard") cfgReader := &configReader{path: configDirectory, log: logger, orgService: orgService} configs, err := cfgReader.readConfig(ctx) @@ -47,7 +48,7 @@ func New(ctx context.Context, configDirectory string, provisioner dashboards.Das return nil, fmt.Errorf("%v: %w", "Failed to read dashboards config", err) } - fileReaders, err := getFileReaders(configs, logger, provisioner, dashboardStore) + fileReaders, err := getFileReaders(configs, logger, provisioner, dashboardStore, folderService) if err != nil { return nil, fmt.Errorf("%v: %w", "Failed to initialize file readers", err) } @@ -66,6 +67,8 @@ func New(ctx context.Context, configDirectory string, provisioner dashboards.Das // Provision scans the disk for dashboards and updates // the database with the latest versions of those dashboards. func (provider *Provisioner) Provision(ctx context.Context) error { + provider.log.Info("starting to provision dashboards") + for _, reader := range provider.fileReaders { if err := reader.walkDisk(ctx); err != nil { if os.IsNotExist(err) { @@ -79,6 +82,7 @@ func (provider *Provisioner) Provision(ctx context.Context) error { } provider.duplicateValidator.validate() + provider.log.Info("finished to provision dashboards") return nil } @@ -127,14 +131,24 @@ func (provider *Provisioner) GetAllowUIUpdatesFromConfig(name string) bool { } func getFileReaders( - configs []*config, logger log.Logger, service dashboards.DashboardProvisioningService, store utils.DashboardStore, + configs []*config, + logger log.Logger, + service dashboards.DashboardProvisioningService, + store utils.DashboardStore, + folderService folder.Service, ) ([]*FileReader, error) { var readers []*FileReader for _, config := range configs { switch config.Type { case "file": - fileReader, err := NewDashboardFileReader(config, logger.New("type", config.Type, "name", config.Name), service, store) + fileReader, err := NewDashboardFileReader( + config, + logger.New("type", config.Type, "name", config.Name), + service, + store, + folderService, + ) if err != nil { return nil, fmt.Errorf("failed to create file reader for config %v: %w", config.Name, err) } diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index df34fc8e686..ab88bc49ef9 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/pkg/services/provisioning/dashboards/file_reader.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/provisioning/utils" "github.com/grafana/grafana/pkg/util" ) @@ -34,6 +35,7 @@ type FileReader struct { dashboardProvisioningService dashboards.DashboardProvisioningService dashboardStore utils.DashboardStore FoldersFromFilesStructure bool + folderService folder.Service mux sync.RWMutex usageTracker *usageTracker @@ -41,7 +43,8 @@ type FileReader struct { } // NewDashboardFileReader returns a new filereader based on `config` -func NewDashboardFileReader(cfg *config, log log.Logger, service dashboards.DashboardProvisioningService, dashboardStore utils.DashboardStore) (*FileReader, error) { +func NewDashboardFileReader(cfg *config, log log.Logger, service dashboards.DashboardProvisioningService, + dashboardStore utils.DashboardStore, folderService folder.Service) (*FileReader, error) { var path string path, ok := cfg.Options["path"].(string) if !ok { @@ -64,6 +67,7 @@ func NewDashboardFileReader(cfg *config, log log.Logger, service dashboards.Dash log: log, dashboardProvisioningService: service, dashboardStore: dashboardStore, + folderService: folderService, FoldersFromFilesStructure: foldersFromFilesStructure, usageTracker: newUsageTracker(), }, nil @@ -333,22 +337,23 @@ func (fr *FileReader) getOrCreateFolder(ctx context.Context, cfg *config, servic // dashboard folder not found. create one. if errors.Is(err, dashboards.ErrDashboardNotFound) { - dash := &dashboards.SaveDashboardDTO{} - dash.Dashboard = dashboards.NewDashboardFolder(folderName) - dash.Dashboard.IsFolder = true - dash.Overwrite = true - dash.OrgID = cfg.OrgID // set dashboard folderUid if given if cfg.FolderUID == accesscontrol.GeneralFolderUID { return 0, "", dashboards.ErrFolderInvalidUID } - dash.Dashboard.SetUID(cfg.FolderUID) - dbDash, err := service.SaveFolderForProvisionedDashboards(ctx, dash) + + createCmd := &folder.CreateFolderCommand{ + OrgID: cfg.OrgID, + UID: cfg.FolderUID, + Title: folderName, + } + + f, err := service.SaveFolderForProvisionedDashboards(ctx, createCmd) if err != nil { return 0, "", err } - - return dbDash.ID, dbDash.UID, nil + // nolint:staticcheck + return f.ID, f.UID, nil } if !result.IsFolder { diff --git a/pkg/services/provisioning/dashboards/file_reader_symlink_test.go b/pkg/services/provisioning/dashboards/file_reader_symlink_test.go index 911d09a953b..3085da3cd75 100644 --- a/pkg/services/provisioning/dashboards/file_reader_symlink_test.go +++ b/pkg/services/provisioning/dashboards/file_reader_symlink_test.go @@ -26,7 +26,7 @@ func TestProvisionedSymlinkedFolder(t *testing.T) { Options: map[string]any{"path": symlinkedFolder}, } - reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil) + reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil) if err != nil { t.Error("expected err to be nil") } diff --git a/pkg/services/provisioning/dashboards/file_reader_test.go b/pkg/services/provisioning/dashboards/file_reader_test.go index d886b11fe8b..ac55b38e851 100644 --- a/pkg/services/provisioning/dashboards/file_reader_test.go +++ b/pkg/services/provisioning/dashboards/file_reader_test.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/util" ) @@ -41,7 +42,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) { t.Run("using path parameter", func(t *testing.T) { cfg := setup() cfg.Options["path"] = defaultDashboards - reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil) + reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil) require.NoError(t, err) require.NotEqual(t, reader.Path, "") }) @@ -49,7 +50,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) { t.Run("using folder as options", func(t *testing.T) { cfg := setup() cfg.Options["folder"] = defaultDashboards - reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil) + reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil) require.NoError(t, err) require.NotEqual(t, reader.Path, "") }) @@ -58,7 +59,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) { cfg := setup() cfg.Options["path"] = foldersFromFilesStructure cfg.Options["foldersFromFilesStructure"] = true - reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil) + reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil) require.NoError(t, err) require.NotEqual(t, reader.Path, "") }) @@ -71,7 +72,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) { } cfg.Options["folder"] = fullPath - reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil) + reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil) require.NoError(t, err) require.Equal(t, reader.Path, fullPath) @@ -81,7 +82,7 @@ func TestCreatingNewDashboardFileReader(t *testing.T) { t.Run("using relative path", func(t *testing.T) { cfg := setup() cfg.Options["folder"] = defaultDashboards - reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil) + reader, err := NewDashboardFileReader(cfg, log.New("test-logger"), nil, nil, nil) require.NoError(t, err) resolvedPath := reader.resolvedPath() @@ -113,10 +114,10 @@ func TestDashboardFileReader(t *testing.T) { cfg.Folder = "Team A" fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(nil, nil).Once() - fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{ID: 1}, nil).Once() + fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Once() fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{ID: 2}, nil).Times(2) - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -136,7 +137,7 @@ func TestDashboardFileReader(t *testing.T) { inserted++ }) - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -173,7 +174,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -201,7 +202,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once() fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -236,7 +237,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -264,7 +265,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(provisionedDashboard, nil).Once() fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -279,7 +280,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(nil, nil).Once() fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -293,10 +294,10 @@ func TestDashboardFileReader(t *testing.T) { cfg.Options["foldersFromFilesStructure"] = true fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(nil, nil).Once() - fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(2) + fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Times(2) fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(3) - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) reader.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -313,7 +314,7 @@ func TestDashboardFileReader(t *testing.T) { Folder: "", } - _, err := NewDashboardFileReader(cfg, logger, nil, nil) + _, err := NewDashboardFileReader(cfg, logger, nil, nil, nil) require.NotNil(t, err) }) @@ -321,7 +322,7 @@ func TestDashboardFileReader(t *testing.T) { setup() cfg.Options["path"] = brokenDashboards - _, err := NewDashboardFileReader(cfg, logger, nil, nil) + _, err := NewDashboardFileReader(cfg, logger, nil, nil, nil) require.NoError(t, err) }) @@ -331,17 +332,17 @@ func TestDashboardFileReader(t *testing.T) { cfg2 := &config{Name: "2", Type: "file", OrgID: 1, Folder: "f2", Options: map[string]any{"path": containingID}} fakeService.On("GetProvisionedDashboardData", mock.Anything, mock.AnythingOfType("string")).Return(nil, nil).Times(2) - fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(2) + fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Times(2) fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(2) - reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore) + reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil) reader1.dashboardProvisioningService = fakeService require.NoError(t, err) err = reader1.walkDisk(context.Background()) require.NoError(t, err) - reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore) + reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil) reader2.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -361,7 +362,7 @@ func TestDashboardFileReader(t *testing.T) { "folder": defaultDashboards, }, } - r, err := NewDashboardFileReader(cfg, logger, nil, nil) + r, err := NewDashboardFileReader(cfg, logger, nil, nil, nil) require.NoError(t, err) _, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder) @@ -379,9 +380,9 @@ func TestDashboardFileReader(t *testing.T) { "folder": defaultDashboards, }, } - fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{ID: 1}, nil).Once() + fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Once() - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) require.NoError(t, err) _, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder) @@ -401,7 +402,7 @@ func TestDashboardFileReader(t *testing.T) { }, } - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) require.NoError(t, err) _, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder) @@ -456,7 +457,7 @@ func TestDashboardFileReader(t *testing.T) { cfg.DisableDeletion = true - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) require.NoError(t, err) reader.dashboardProvisioningService = fakeService @@ -471,7 +472,7 @@ func TestDashboardFileReader(t *testing.T) { fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() fakeService.On("DeleteProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() - reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) reader.dashboardProvisioningService = fakeService require.NoError(t, err) diff --git a/pkg/services/provisioning/dashboards/validator_test.go b/pkg/services/provisioning/dashboards/validator_test.go index 18975387f73..de8d438d02f 100644 --- a/pkg/services/provisioning/dashboards/validator_test.go +++ b/pkg/services/provisioning/dashboards/validator_test.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" ) const ( @@ -34,9 +35,9 @@ func TestDuplicatesValidator(t *testing.T) { const folderName = "duplicates-validator-folder" fakeStore := &fakeDashboardStore{} - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) require.NoError(t, err) - fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(6) + fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Times(6) fakeService.On("GetProvisionedDashboardData", mock.Anything, mock.AnythingOfType("string")).Return([]*dashboards.DashboardProvisioning{}, nil).Times(4) fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(5) _, folderUID, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, folderName) @@ -53,11 +54,11 @@ func TestDuplicatesValidator(t *testing.T) { Options: map[string]any{"path": dashboardContainingUID}, } - reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore) + reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil) reader1.dashboardProvisioningService = fakeService require.NoError(t, err) - reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore) + reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil) reader2.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -90,7 +91,7 @@ func TestDuplicatesValidator(t *testing.T) { const folderName = "duplicates-validator-folder" fakeStore := &fakeDashboardStore{} - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) require.NoError(t, err) _, folderUID, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, folderName) require.NoError(t, err) @@ -106,11 +107,11 @@ func TestDuplicatesValidator(t *testing.T) { Options: map[string]any{"path": dashboardContainingUID}, } - reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore) + reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil) reader1.dashboardProvisioningService = fakeService require.NoError(t, err) - reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore) + reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil) reader2.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -150,7 +151,7 @@ func TestDuplicatesValidator(t *testing.T) { }) t.Run("Duplicates validator should restrict write access only for readers with duplicates", func(t *testing.T) { - fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(5) + fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Times(5) fakeService.On("GetProvisionedDashboardData", mock.Anything, mock.AnythingOfType("string")).Return([]*dashboards.DashboardProvisioning{}, nil).Times(3) fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(5) fakeStore := &fakeDashboardStore{} @@ -167,15 +168,15 @@ func TestDuplicatesValidator(t *testing.T) { Name: "third", Type: "file", OrgID: 2, Folder: "duplicates-validator-folder", Options: map[string]any{"path": twoDashboardsWithUID}, } - reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore) + reader1, err := NewDashboardFileReader(cfg1, logger, nil, fakeStore, nil) reader1.dashboardProvisioningService = fakeService require.NoError(t, err) - reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore) + reader2, err := NewDashboardFileReader(cfg2, logger, nil, fakeStore, nil) reader2.dashboardProvisioningService = fakeService require.NoError(t, err) - reader3, err := NewDashboardFileReader(cfg3, logger, nil, fakeStore) + reader3, err := NewDashboardFileReader(cfg3, logger, nil, fakeStore, nil) reader3.dashboardProvisioningService = fakeService require.NoError(t, err) @@ -192,7 +193,7 @@ func TestDuplicatesValidator(t *testing.T) { duplicates := duplicateValidator.getDuplicates() - r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) + r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil) require.NoError(t, err) _, folderUID, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg1.Folder) require.NoError(t, err) @@ -209,7 +210,7 @@ func TestDuplicatesValidator(t *testing.T) { sort.Strings(titleUsageReaders) require.Equal(t, []string{"first"}, titleUsageReaders) - r, err = NewDashboardFileReader(cfg3, logger, nil, fakeStore) + r, err = NewDashboardFileReader(cfg3, logger, nil, fakeStore, nil) require.NoError(t, err) _, folderUID, err = r.getOrCreateFolder(context.Background(), cfg3, fakeService, cfg3.Folder) require.NoError(t, err) diff --git a/pkg/services/provisioning/provisioning.go b/pkg/services/provisioning/provisioning.go index 873295b3db3..b4394df1391 100644 --- a/pkg/services/provisioning/provisioning.go +++ b/pkg/services/provisioning/provisioning.go @@ -75,6 +75,7 @@ func ProvideService( secretService: secrectService, log: log.New("provisioning"), orgService: orgService, + folderService: folderService, } return s, nil } @@ -145,6 +146,7 @@ type ProvisioningServiceImpl struct { searchService searchV2.SearchService quotaService quota.Service secretService secrets.Service + folderService folder.Service } func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) error { @@ -239,7 +241,7 @@ func (ps *ProvisioningServiceImpl) ProvisionNotifications(ctx context.Context) e func (ps *ProvisioningServiceImpl) ProvisionDashboards(ctx context.Context) error { dashboardPath := filepath.Join(ps.Cfg.ProvisioningPath, "dashboards") - dashProvisioner, err := ps.newDashboardProvisioner(ctx, dashboardPath, ps.dashboardProvisioningService, ps.orgService, ps.dashboardService) + dashProvisioner, err := ps.newDashboardProvisioner(ctx, dashboardPath, ps.dashboardProvisioningService, ps.orgService, ps.dashboardService, ps.folderService) if err != nil { return fmt.Errorf("%v: %w", "Failed to create provisioner", err) } diff --git a/pkg/services/provisioning/provisioning_test.go b/pkg/services/provisioning/provisioning_test.go index e7626af9d37..356cc85537b 100644 --- a/pkg/services/provisioning/provisioning_test.go +++ b/pkg/services/provisioning/provisioning_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" dashboardstore "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/provisioning/dashboards" "github.com/grafana/grafana/pkg/services/provisioning/utils" @@ -95,7 +96,7 @@ func setup() *serviceTestStruct { } serviceTest.service = newProvisioningServiceImpl( - func(context.Context, string, dashboardstore.DashboardProvisioningService, org.Service, utils.DashboardStore) (dashboards.DashboardProvisioner, error) { + func(context.Context, string, dashboardstore.DashboardProvisioningService, org.Service, utils.DashboardStore, folder.Service) (dashboards.DashboardProvisioner, error) { return serviceTest.mock, nil }, nil,