mirror of https://github.com/grafana/grafana.git
				
				
				
			NestedFolders: Add library panels counting and deletion to folder registry (#69149)
* Expose library element service's folder service * Register library panels, add count implementation * Expand folder counts test * Update registry deletion method interface * Allow getting library elements from any folder * Add test for library panel deletion * Add test for library panel counting
This commit is contained in:
		
							parent
							
								
									32e2304f10
								
							
						
					
					
						commit
						20ffbbc41e
					
				|  | @ -1359,7 +1359,7 @@ func (l *mockLibraryElementService) CreateElement(c context.Context, signedInUse | |||
| } | ||||
| 
 | ||||
| // GetElement gets an element from a UID.
 | ||||
| func (l *mockLibraryElementService) GetElement(c context.Context, signedInUser *user.SignedInUser, UID string) (model.LibraryElementDTO, error) { | ||||
| func (l *mockLibraryElementService) GetElement(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { | ||||
| 	return model.LibraryElementDTO{}, nil | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -281,6 +281,12 @@ func (hs *HTTPServer) DeleteFolder(c *contextmodel.ReqContext) response.Response | |||
| 		} | ||||
| 		return apierrors.ToFolderErrorResponse(err) | ||||
| 	} | ||||
| 	/* TODO: after a decision regarding folder deletion permissions has been made | ||||
| 	(https://github.com/grafana/grafana-enterprise/issues/5144),
 | ||||
| 	remove the previous call to hs.LibraryElementService.DeleteLibraryElementsInFolder | ||||
| 	and remove "user" from the signature of DeleteInFolder in the folder RegistryService. | ||||
| 	Context: https://github.com/grafana/grafana/pull/69149#discussion_r1235057903
 | ||||
| 	*/ | ||||
| 
 | ||||
| 	uid := web.Params(c.Req)[":uid"] | ||||
| 	err = hs.folderService.Delete(c.Req.Context(), &folder.DeleteFolderCommand{UID: uid, OrgID: c.OrgID, ForceDeleteRules: c.QueryBool("forceDeleteRules"), SignedInUser: c.SignedInUser}) | ||||
|  |  | |||
|  | @ -635,7 +635,7 @@ func (dr DashboardServiceImpl) CountInFolder(ctx context.Context, orgID int64, f | |||
| 	return dr.dashboardStore.CountDashboardsInFolder(ctx, &dashboards.CountDashboardsInFolderRequest{FolderID: folder.ID, OrgID: orgID}) | ||||
| } | ||||
| 
 | ||||
| func (dr *DashboardServiceImpl) DeleteInFolder(ctx context.Context, orgID int64, folderUID string) error { | ||||
| func (dr *DashboardServiceImpl) DeleteInFolder(ctx context.Context, orgID int64, folderUID string, u *user.SignedInUser) error { | ||||
| 	return dr.dashboardStore.DeleteDashboardsInFolder(ctx, &dashboards.DeleteDashboardsInFolderRequest{FolderUID: folderUID, OrgID: orgID}) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -244,7 +244,7 @@ func TestDashboardService(t *testing.T) { | |||
| 		t.Run("Delete dashboards in folder", func(t *testing.T) { | ||||
| 			args := &dashboards.DeleteDashboardsInFolderRequest{OrgID: 1, FolderUID: "uid"} | ||||
| 			fakeStore.On("DeleteDashboardsInFolder", mock.Anything, args).Return(nil).Once() | ||||
| 			err := service.DeleteInFolder(context.Background(), 1, "uid") | ||||
| 			err := service.DeleteInFolder(context.Background(), 1, "uid", nil) | ||||
| 			require.NoError(t, err) | ||||
| 		}) | ||||
| 	}) | ||||
|  |  | |||
|  | @ -509,7 +509,7 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e | |||
| 			} | ||||
| 
 | ||||
| 			if cmd.ForceDeleteRules { | ||||
| 				if err := s.deleteChildrenInFolder(ctx, dashFolder.OrgID, dashFolder.UID); err != nil { | ||||
| 				if err := s.deleteChildrenInFolder(ctx, dashFolder.OrgID, dashFolder.UID, cmd.SignedInUser); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
|  | @ -525,9 +525,9 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e | |||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (s *Service) deleteChildrenInFolder(ctx context.Context, orgID int64, folderUID string) error { | ||||
| func (s *Service) deleteChildrenInFolder(ctx context.Context, orgID int64, folderUID string, user *user.SignedInUser) error { | ||||
| 	for _, v := range s.registry { | ||||
| 		if err := v.DeleteInFolder(ctx, orgID, folderUID); err != nil { | ||||
| 		if err := v.DeleteInFolder(ctx, orgID, folderUID, user); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import ( | |||
| 	"github.com/stretchr/testify/mock" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/api/routing" | ||||
| 	"github.com/grafana/grafana/pkg/bus" | ||||
| 	"github.com/grafana/grafana/pkg/infra/db" | ||||
| 	"github.com/grafana/grafana/pkg/infra/db/dbtest" | ||||
|  | @ -28,10 +29,14 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/services/folder" | ||||
| 	"github.com/grafana/grafana/pkg/services/folder/foldertest" | ||||
| 	"github.com/grafana/grafana/pkg/services/guardian" | ||||
| 	"github.com/grafana/grafana/pkg/services/libraryelements" | ||||
| 	"github.com/grafana/grafana/pkg/services/libraryelements/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/librarypanels" | ||||
| 	"github.com/grafana/grafana/pkg/services/ngalert/models" | ||||
| 	ngstore "github.com/grafana/grafana/pkg/services/ngalert/store" | ||||
| 	"github.com/grafana/grafana/pkg/services/quota/quotatest" | ||||
| 	"github.com/grafana/grafana/pkg/services/sqlstore" | ||||
| 	"github.com/grafana/grafana/pkg/services/store/entity" | ||||
| 	"github.com/grafana/grafana/pkg/services/tag/tagimpl" | ||||
| 	"github.com/grafana/grafana/pkg/services/user" | ||||
| 	"github.com/grafana/grafana/pkg/setting" | ||||
|  | @ -356,7 +361,9 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 	} | ||||
| 
 | ||||
| 	signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{ | ||||
| 		orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}}, | ||||
| 		orgID: { | ||||
| 			dashboards.ActionFoldersCreate: {}, | ||||
| 			dashboards.ActionFoldersWrite:  {dashboards.ScopeFoldersAll}}, | ||||
| 	}} | ||||
| 	createCmd := folder.CreateFolderCommand{ | ||||
| 		OrgID:        orgID, | ||||
|  | @ -364,6 +371,20 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 		SignedInUser: &signedInUser, | ||||
| 	} | ||||
| 
 | ||||
| 	libraryElementCmd := model.CreateLibraryElementCommand{ | ||||
| 		Model: []byte(` | ||||
| 		{ | ||||
| 		  "datasource": "${DS_GDEV-TESTDATA}", | ||||
| 		  "id": 1, | ||||
| 		  "title": "Text - Library Panel", | ||||
| 		  "type": "text", | ||||
| 		  "description": "A description" | ||||
| 		} | ||||
| 	`), | ||||
| 		Kind: int64(model.PanelElement), | ||||
| 	} | ||||
| 	routeRegister := routing.NewRouteRegister() | ||||
| 
 | ||||
| 	folderPermissions := acmock.NewMockedPermissionsService() | ||||
| 	dashboardPermissions := acmock.NewMockedPermissionsService() | ||||
| 
 | ||||
|  | @ -371,7 +392,12 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 		depth := 5 | ||||
| 		t.Run("With nested folder feature flag on", func(t *testing.T) { | ||||
| 			origNewGuardian := guardian.New | ||||
| 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true}) | ||||
| 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ | ||||
| 				CanSaveValue: true, | ||||
| 				CanViewValue: true, | ||||
| 				// CanEditValue is required to create library elements
 | ||||
| 				CanEditValue: true, | ||||
| 			}) | ||||
| 
 | ||||
| 			dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn) | ||||
| 			require.NoError(t, err) | ||||
|  | @ -379,6 +405,10 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 			alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOn, featuresFlagOn) | ||||
| 			lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOn) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd) | ||||
| 
 | ||||
| 			parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0]) | ||||
|  | @ -390,6 +420,13 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 			_ = createRule(t, alertStore, parent.UID, "parent alert") | ||||
| 			_ = createRule(t, alertStore, subfolder.UID, "sub alert") | ||||
| 
 | ||||
| 			libraryElementCmd.FolderID = parent.ID | ||||
| 			_, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) | ||||
| 			require.NoError(t, err) | ||||
| 			libraryElementCmd.FolderID = subfolder.ID | ||||
| 			_, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			countCmd := folder.GetDescendantCountsQuery{ | ||||
| 				UID:          &ancestorUIDs[0], | ||||
| 				OrgID:        orgID, | ||||
|  | @ -397,9 +434,10 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 			} | ||||
| 			m, err := serviceWithFlagOn.GetDescendantCounts(context.Background(), &countCmd) | ||||
| 			require.NoError(t, err) | ||||
| 			require.Equal(t, int64(depth-1), m["folder"]) | ||||
| 			require.Equal(t, int64(2), m["dashboard"]) | ||||
| 			require.Equal(t, int64(2), m["alertrule"]) | ||||
| 			require.Equal(t, int64(depth-1), m[entity.StandardKindFolder]) | ||||
| 			require.Equal(t, int64(2), m[entity.StandardKindDashboard]) | ||||
| 			require.Equal(t, int64(2), m[entity.StandardKindAlertRule]) | ||||
| 			require.Equal(t, int64(2), m[entity.StandardKindLibraryPanel]) | ||||
| 
 | ||||
| 			t.Cleanup(func() { | ||||
| 				guardian.New = origNewGuardian | ||||
|  | @ -428,7 +466,12 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 			} | ||||
| 
 | ||||
| 			origNewGuardian := guardian.New | ||||
| 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true}) | ||||
| 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ | ||||
| 				CanSaveValue: true, | ||||
| 				CanViewValue: true, | ||||
| 				// CanEditValue is required to create library elements
 | ||||
| 				CanEditValue: true, | ||||
| 			}) | ||||
| 
 | ||||
| 			dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff, | ||||
| 				folderPermissions, dashboardPermissions, ac, serviceWithFlagOff) | ||||
|  | @ -437,6 +480,10 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 			alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, ac, dashSrv) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOff, featuresFlagOff) | ||||
| 			lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOff) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd) | ||||
| 
 | ||||
| 			parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0]) | ||||
|  | @ -448,6 +495,13 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 			_ = createRule(t, alertStore, parent.UID, "parent alert") | ||||
| 			_ = createRule(t, alertStore, subfolder.UID, "sub alert") | ||||
| 
 | ||||
| 			libraryElementCmd.FolderID = parent.ID | ||||
| 			_, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) | ||||
| 			require.NoError(t, err) | ||||
| 			libraryElementCmd.FolderID = subfolder.ID | ||||
| 			_, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			countCmd := folder.GetDescendantCountsQuery{ | ||||
| 				UID:          &ancestorUIDs[0], | ||||
| 				OrgID:        orgID, | ||||
|  | @ -455,9 +509,10 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 			} | ||||
| 			m, err := serviceWithFlagOff.GetDescendantCounts(context.Background(), &countCmd) | ||||
| 			require.NoError(t, err) | ||||
| 			require.Equal(t, int64(0), m["folder"]) | ||||
| 			require.Equal(t, int64(1), m["dashboard"]) | ||||
| 			require.Equal(t, int64(1), m["alertrule"]) | ||||
| 			require.Equal(t, int64(0), m[entity.StandardKindFolder]) | ||||
| 			require.Equal(t, int64(1), m[entity.StandardKindDashboard]) | ||||
| 			require.Equal(t, int64(1), m[entity.StandardKindAlertRule]) | ||||
| 			require.Equal(t, int64(1), m[entity.StandardKindLibraryPanel]) | ||||
| 
 | ||||
| 			t.Cleanup(func() { | ||||
| 				guardian.New = origNewGuardian | ||||
|  | @ -470,169 +525,158 @@ func TestIntegrationNestedFolderService(t *testing.T) { | |||
| 	}) | ||||
| 
 | ||||
| 	t.Run("Should delete folders", func(t *testing.T) { | ||||
| 		t.Run("With nested folder feature flag on", func(t *testing.T) { | ||||
| 			dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn) | ||||
| 			require.NoError(t, err) | ||||
| 		featuresFlagOff := featuremgmt.WithFeatures() | ||||
| 		serviceWithFlagOff := &Service{ | ||||
| 			cfg:                  cfg, | ||||
| 			log:                  log.New("test-folder-service"), | ||||
| 			dashboardFolderStore: folderStore, | ||||
| 			features:             featuresFlagOff, | ||||
| 			bus:                  b, | ||||
| 			db:                   db, | ||||
| 			registry:             make(map[string]folder.RegistryService), | ||||
| 		} | ||||
| 
 | ||||
| 			alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv) | ||||
| 			require.NoError(t, err) | ||||
| 			t.Run("With force deletion of rules", func(t *testing.T) { | ||||
| 		testCases := []struct { | ||||
| 			service           *Service | ||||
| 			featuresFlag      *featuremgmt.FeatureManager | ||||
| 			prefix            string | ||||
| 			depth             int | ||||
| 			forceDelete       bool | ||||
| 			deletionErr       error | ||||
| 			dashboardErr      error | ||||
| 			folderErr         error | ||||
| 			libPanelParentErr error | ||||
| 			libPanelSubErr    error | ||||
| 			desc              string | ||||
| 		}{ | ||||
| 			{ | ||||
| 				service:           serviceWithFlagOn, | ||||
| 				featuresFlag:      featuresFlagOn, | ||||
| 				prefix:            "flagon-force", | ||||
| 				depth:             3, | ||||
| 				forceDelete:       true, | ||||
| 				dashboardErr:      dashboards.ErrFolderNotFound, | ||||
| 				folderErr:         folder.ErrFolderNotFound, | ||||
| 				libPanelParentErr: model.ErrLibraryElementNotFound, | ||||
| 				libPanelSubErr:    model.ErrLibraryElementNotFound, | ||||
| 				desc:              "With nested folder feature flag on and force deletion of rules", | ||||
| 			}, | ||||
| 			{ | ||||
| 				service:      serviceWithFlagOn, | ||||
| 				featuresFlag: featuresFlagOn, | ||||
| 				prefix:       "flagon-noforce", | ||||
| 				depth:        3, | ||||
| 				forceDelete:  false, | ||||
| 				deletionErr:  dashboards.ErrFolderContainsAlertRules, | ||||
| 				desc:         "With nested folder feature flag on and no force deletion of rules", | ||||
| 			}, | ||||
| 			{ | ||||
| 				service:           serviceWithFlagOff, | ||||
| 				featuresFlag:      featuresFlagOff, | ||||
| 				prefix:            "flagoff-force", | ||||
| 				depth:             1, | ||||
| 				forceDelete:       true, | ||||
| 				dashboardErr:      dashboards.ErrFolderNotFound, | ||||
| 				libPanelParentErr: model.ErrLibraryElementNotFound, | ||||
| 				desc:              "With nested folder feature flag off and force deletion of rules", | ||||
| 			}, | ||||
| 			{ | ||||
| 				service:      serviceWithFlagOff, | ||||
| 				featuresFlag: featuresFlagOff, | ||||
| 				prefix:       "flagoff-noforce", | ||||
| 				depth:        1, | ||||
| 				forceDelete:  false, | ||||
| 				deletionErr:  dashboards.ErrFolderContainsAlertRules, | ||||
| 				desc:         "With nested folder feature flag off and no force deletion of rules", | ||||
| 			}, | ||||
| 		} | ||||
| 
 | ||||
| 		for _, tc := range testCases { | ||||
| 			t.Run(tc.desc, func(t *testing.T) { | ||||
| 				origNewGuardian := guardian.New | ||||
| 				guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true}) | ||||
| 				guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ | ||||
| 					CanSaveValue: true, | ||||
| 					CanViewValue: true, | ||||
| 					// CanEditValue is required to create library elements
 | ||||
| 					CanEditValue: true, | ||||
| 				}) | ||||
| 
 | ||||
| 				ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "with-force", createCmd) | ||||
| 				elementService := libraryelements.ProvideService(cfg, db, routeRegister, tc.service, tc.featuresFlag) | ||||
| 				lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, tc.service) | ||||
| 				require.NoError(t, err) | ||||
| 
 | ||||
| 				dashStore, err := database.ProvideDashboardStore(db, db.Cfg, tc.featuresFlag, tagimpl.ProvideService(db, db.Cfg), quotaService) | ||||
| 				require.NoError(t, err) | ||||
| 				nestedFolderStore := ProvideStore(db, db.Cfg, tc.featuresFlag) | ||||
| 				tc.service.dashboardStore = dashStore | ||||
| 				tc.service.store = nestedFolderStore | ||||
| 
 | ||||
| 				dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, tc.featuresFlag, folderPermissions, dashboardPermissions, ac, tc.service) | ||||
| 				require.NoError(t, err) | ||||
| 				alertStore, err := ngstore.ProvideDBStore(cfg, tc.featuresFlag, db, tc.service, ac, dashSrv) | ||||
| 				require.NoError(t, err) | ||||
| 
 | ||||
| 				ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, tc.depth, tc.prefix, createCmd) | ||||
| 
 | ||||
| 				parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0]) | ||||
| 				require.NoError(t, err) | ||||
| 				subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1]) | ||||
| 				require.NoError(t, err) | ||||
| 				_ = createRule(t, alertStore, parent.UID, "parent alert") | ||||
| 				_ = createRule(t, alertStore, subfolder.UID, "sub alert") | ||||
| 
 | ||||
| 				var ( | ||||
| 					subfolder *folder.Folder | ||||
| 					subPanel  model.LibraryElementDTO | ||||
| 				) | ||||
| 				if tc.depth > 1 { | ||||
| 					subfolder, err = serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1]) | ||||
| 					require.NoError(t, err) | ||||
| 					_ = createRule(t, alertStore, subfolder.UID, "sub alert") | ||||
| 					libraryElementCmd.FolderID = subfolder.ID | ||||
| 					subPanel, err = lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) | ||||
| 					require.NoError(t, err) | ||||
| 				} | ||||
| 
 | ||||
| 				libraryElementCmd.FolderID = parent.ID | ||||
| 				parentPanel, err := lps.LibraryElementService.CreateElement(context.Background(), &signedInUser, libraryElementCmd) | ||||
| 				require.NoError(t, err) | ||||
| 
 | ||||
| 				deleteCmd := folder.DeleteFolderCommand{ | ||||
| 					UID:              ancestorUIDs[0], | ||||
| 					OrgID:            orgID, | ||||
| 					SignedInUser:     &signedInUser, | ||||
| 					ForceDeleteRules: true, | ||||
| 					ForceDeleteRules: tc.forceDelete, | ||||
| 				} | ||||
| 				err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd) | ||||
| 				require.NoError(t, err) | ||||
| 
 | ||||
| 				err = tc.service.Delete(context.Background(), &deleteCmd) | ||||
| 				require.ErrorIs(t, err, tc.deletionErr) | ||||
| 
 | ||||
| 				for i, uid := range ancestorUIDs { | ||||
| 					// dashboard table
 | ||||
| 					_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid) | ||||
| 					require.ErrorIs(t, err, dashboards.ErrFolderNotFound) | ||||
| 					_, err := tc.service.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid) | ||||
| 					require.ErrorIs(t, err, tc.dashboardErr) | ||||
| 					// folder table
 | ||||
| 					_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID}) | ||||
| 					require.ErrorIs(t, err, folder.ErrFolderNotFound) | ||||
| 					_, err = tc.service.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID}) | ||||
| 					require.ErrorIs(t, err, tc.folderErr) | ||||
| 				} | ||||
| 
 | ||||
| 				_, err = lps.LibraryElementService.GetElement(context.Background(), &signedInUser, model.GetLibraryElementCommand{ | ||||
| 					FolderName: parent.Title, | ||||
| 					FolderID:   parent.ID, | ||||
| 					UID:        parentPanel.UID, | ||||
| 				}) | ||||
| 				require.ErrorIs(t, err, tc.libPanelParentErr) | ||||
| 				if tc.depth > 1 { | ||||
| 					_, err = lps.LibraryElementService.GetElement(context.Background(), &signedInUser, model.GetLibraryElementCommand{ | ||||
| 						FolderName: subfolder.Title, | ||||
| 						FolderID:   subfolder.ID, | ||||
| 						UID:        subPanel.UID, | ||||
| 					}) | ||||
| 					require.ErrorIs(t, err, tc.libPanelSubErr) | ||||
| 				} | ||||
| 				t.Cleanup(func() { | ||||
| 					guardian.New = origNewGuardian | ||||
| 				}) | ||||
| 			}) | ||||
| 			t.Run("Without force deletion of rules", func(t *testing.T) { | ||||
| 				origNewGuardian := guardian.New | ||||
| 				guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true}) | ||||
| 
 | ||||
| 				ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "without-force", createCmd) | ||||
| 
 | ||||
| 				parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0]) | ||||
| 				require.NoError(t, err) | ||||
| 				subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1]) | ||||
| 				require.NoError(t, err) | ||||
| 				_ = createRule(t, alertStore, parent.UID, "parent alert") | ||||
| 				_ = createRule(t, alertStore, subfolder.UID, "sub alert") | ||||
| 
 | ||||
| 				deleteCmd := folder.DeleteFolderCommand{ | ||||
| 					UID:              ancestorUIDs[0], | ||||
| 					OrgID:            orgID, | ||||
| 					SignedInUser:     &signedInUser, | ||||
| 					ForceDeleteRules: false, | ||||
| 				} | ||||
| 				err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd) | ||||
| 				require.Error(t, dashboards.ErrFolderContainsAlertRules, err) | ||||
| 
 | ||||
| 				for i, uid := range ancestorUIDs { | ||||
| 					// dashboard table
 | ||||
| 					_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid) | ||||
| 					require.NoError(t, err) | ||||
| 					// folder table
 | ||||
| 					_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID}) | ||||
| 					require.NoError(t, err) | ||||
| 				} | ||||
| 				t.Cleanup(func() { | ||||
| 					guardian.New = origNewGuardian | ||||
| 				}) | ||||
| 			}) | ||||
| 		}) | ||||
| 		t.Run("With nested folder feature flag off", func(t *testing.T) { | ||||
| 			featuresFlagOff := featuremgmt.WithFeatures() | ||||
| 			dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOff, tagimpl.ProvideService(db, db.Cfg), quotaService) | ||||
| 			require.NoError(t, err) | ||||
| 			nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOff) | ||||
| 
 | ||||
| 			dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn) | ||||
| 			require.NoError(t, err) | ||||
| 			alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOn, ac, dashSrv) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			serviceWithFlagOff := &Service{ | ||||
| 				cfg:                  cfg, | ||||
| 				log:                  log.New("test-folder-service"), | ||||
| 				dashboardStore:       dashStore, | ||||
| 				dashboardFolderStore: folderStore, | ||||
| 				store:                nestedFolderStore, | ||||
| 				features:             featuresFlagOff, | ||||
| 				bus:                  b, | ||||
| 				db:                   db, | ||||
| 			} | ||||
| 			t.Run("With force deletion of rules", func(t *testing.T) { | ||||
| 				origNewGuardian := guardian.New | ||||
| 				guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true}) | ||||
| 
 | ||||
| 				ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "off-force", createCmd) | ||||
| 
 | ||||
| 				parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0]) | ||||
| 				require.NoError(t, err) | ||||
| 				_ = createRule(t, alertStore, parent.UID, "parent alert") | ||||
| 
 | ||||
| 				deleteCmd := folder.DeleteFolderCommand{ | ||||
| 					UID:              ancestorUIDs[0], | ||||
| 					OrgID:            orgID, | ||||
| 					SignedInUser:     &signedInUser, | ||||
| 					ForceDeleteRules: true, | ||||
| 				} | ||||
| 				err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd) | ||||
| 				require.NoError(t, err) | ||||
| 
 | ||||
| 				// dashboard table
 | ||||
| 				_, err = serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0]) | ||||
| 				require.ErrorIs(t, err, dashboards.ErrFolderNotFound) | ||||
| 				// folder table
 | ||||
| 				_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[0], OrgID: orgID}) | ||||
| 				require.NoError(t, err) | ||||
| 				t.Cleanup(func() { | ||||
| 					guardian.New = origNewGuardian | ||||
| 					for _, uid := range ancestorUIDs { | ||||
| 						err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID) | ||||
| 						require.NoError(t, err) | ||||
| 					} | ||||
| 				}) | ||||
| 			}) | ||||
| 			t.Run("Without force deletion of rules", func(t *testing.T) { | ||||
| 				origNewGuardian := guardian.New | ||||
| 				guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true}) | ||||
| 
 | ||||
| 				ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "off-no-force", createCmd) | ||||
| 
 | ||||
| 				parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0]) | ||||
| 				require.NoError(t, err) | ||||
| 				_ = createRule(t, alertStore, parent.UID, "parent alert") | ||||
| 
 | ||||
| 				deleteCmd := folder.DeleteFolderCommand{ | ||||
| 					UID:              ancestorUIDs[0], | ||||
| 					OrgID:            orgID, | ||||
| 					SignedInUser:     &signedInUser, | ||||
| 					ForceDeleteRules: false, | ||||
| 				} | ||||
| 				err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd) | ||||
| 				require.Error(t, dashboards.ErrFolderContainsAlertRules, err) | ||||
| 
 | ||||
| 				// dashboard table
 | ||||
| 				_, err = serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0]) | ||||
| 				require.NoError(t, err) | ||||
| 				// folder table
 | ||||
| 				_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[0], OrgID: orgID}) | ||||
| 				require.NoError(t, err) | ||||
| 				t.Cleanup(func() { | ||||
| 					guardian.New = origNewGuardian | ||||
| 					for _, uid := range ancestorUIDs { | ||||
| 						err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID) | ||||
| 						require.NoError(t, err) | ||||
| 					} | ||||
| 				}) | ||||
| 			}) | ||||
| 		}) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import ( | |||
| ) | ||||
| 
 | ||||
| type RegistryService interface { | ||||
| 	DeleteInFolder(ctx context.Context, orgID int64, folderUID string) error | ||||
| 	DeleteInFolder(ctx context.Context, orgID int64, folderUID string, user *user.SignedInUser) error | ||||
| 	CountInFolder(ctx context.Context, orgID int64, folderUID string, user *user.SignedInUser) (int64, error) | ||||
| 	Kind() string | ||||
| } | ||||
|  |  | |||
|  | @ -112,7 +112,12 @@ func (l *LibraryElementService) deleteHandler(c *contextmodel.ReqContext) respon | |||
| // 404: notFoundError
 | ||||
| // 500: internalServerError
 | ||||
| func (l *LibraryElementService) getHandler(c *contextmodel.ReqContext) response.Response { | ||||
| 	element, err := l.getLibraryElementByUid(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":uid"]) | ||||
| 	element, err := l.getLibraryElementByUid(c.Req.Context(), c.SignedInUser, | ||||
| 		model.GetLibraryElementCommand{ | ||||
| 			UID:        web.Params(c.Req)[":uid"], | ||||
| 			FolderName: dashboards.RootFolderName, | ||||
| 		}, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return toLibraryElementError(err, "Failed to get library element") | ||||
| 	} | ||||
|  |  | |||
|  | @ -228,7 +228,7 @@ func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedIn | |||
| } | ||||
| 
 | ||||
| // getLibraryElements gets a Library Element where param == value
 | ||||
| func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signedInUser *user.SignedInUser, params []Pair, features featuremgmt.FeatureToggles) ([]model.LibraryElementDTO, error) { | ||||
| func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signedInUser *user.SignedInUser, params []Pair, features featuremgmt.FeatureToggles, cmd model.GetLibraryElementCommand) ([]model.LibraryElementDTO, error) { | ||||
| 	libraryElements := make([]model.LibraryElementWithMeta, 0) | ||||
| 
 | ||||
| 	recursiveQueriesAreSupported, err := store.RecursiveQueriesAreSupported() | ||||
|  | @ -239,10 +239,10 @@ func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signed | |||
| 	err = store.WithDbSession(c, func(session *db.Session) error { | ||||
| 		builder := db.NewSqlBuilder(cfg, features, store.GetDialect(), recursiveQueriesAreSupported) | ||||
| 		builder.Write(selectLibraryElementDTOWithMeta) | ||||
| 		builder.Write(", 'General' as folder_name ") | ||||
| 		builder.Write(", ? as folder_name ", cmd.FolderName) | ||||
| 		builder.Write(", '' as folder_uid ") | ||||
| 		builder.Write(getFromLibraryElementDTOWithMeta(store.GetDialect())) | ||||
| 		writeParamSelectorSQL(&builder, append(params, Pair{"folder_id", 0})...) | ||||
| 		writeParamSelectorSQL(&builder, append(params, Pair{"folder_id", cmd.FolderID})...) | ||||
| 		builder.Write(" UNION ") | ||||
| 		builder.Write(selectLibraryElementDTOWithMeta) | ||||
| 		builder.Write(", dashboard.title as folder_name ") | ||||
|  | @ -303,8 +303,8 @@ func getLibraryElements(c context.Context, store db.DB, cfg *setting.Cfg, signed | |||
| } | ||||
| 
 | ||||
| // getLibraryElementByUid gets a Library Element by uid.
 | ||||
| func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signedInUser *user.SignedInUser, UID string) (model.LibraryElementDTO, error) { | ||||
| 	libraryElements, err := getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{key: "org_id", value: signedInUser.OrgID}, {key: "uid", value: UID}}, l.features) | ||||
| func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { | ||||
| 	libraryElements, err := getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{key: "org_id", value: signedInUser.OrgID}, {key: "uid", value: cmd.UID}}, l.features, cmd) | ||||
| 	if err != nil { | ||||
| 		return model.LibraryElementDTO{}, err | ||||
| 	} | ||||
|  | @ -317,7 +317,10 @@ func (l *LibraryElementService) getLibraryElementByUid(c context.Context, signed | |||
| 
 | ||||
| // getLibraryElementByName gets a Library Element by name.
 | ||||
| func (l *LibraryElementService) getLibraryElementsByName(c context.Context, signedInUser *user.SignedInUser, name string) ([]model.LibraryElementDTO, error) { | ||||
| 	return getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{"org_id", signedInUser.OrgID}, {"name", name}}, l.features) | ||||
| 	return getLibraryElements(c, l.SQLStore, l.Cfg, signedInUser, []Pair{{"org_id", signedInUser.OrgID}, {"name", name}}, l.features, | ||||
| 		model.GetLibraryElementCommand{ | ||||
| 			FolderName: dashboards.RootFolderName, | ||||
| 		}) | ||||
| } | ||||
| 
 | ||||
| // getAllLibraryElements gets all Library Elements.
 | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.Rout | |||
| // Service is a service for operating on library elements.
 | ||||
| type Service interface { | ||||
| 	CreateElement(c context.Context, signedInUser *user.SignedInUser, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) | ||||
| 	GetElement(c context.Context, signedInUser *user.SignedInUser, UID string) (model.LibraryElementDTO, error) | ||||
| 	GetElement(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) | ||||
| 	GetElementsForDashboard(c context.Context, dashboardID int64) (map[string]model.LibraryElementDTO, error) | ||||
| 	ConnectElementsToDashboard(c context.Context, signedInUser *user.SignedInUser, elementUIDs []string, dashboardID int64) error | ||||
| 	DisconnectElementsFromDashboard(c context.Context, dashboardID int64) error | ||||
|  | @ -52,8 +52,8 @@ func (l *LibraryElementService) CreateElement(c context.Context, signedInUser *u | |||
| } | ||||
| 
 | ||||
| // GetElement gets an element from a UID.
 | ||||
| func (l *LibraryElementService) GetElement(c context.Context, signedInUser *user.SignedInUser, UID string) (model.LibraryElementDTO, error) { | ||||
| 	return l.getLibraryElementByUid(c, signedInUser, UID) | ||||
| func (l *LibraryElementService) GetElement(c context.Context, signedInUser *user.SignedInUser, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { | ||||
| 	return l.getLibraryElementByUid(c, signedInUser, cmd) | ||||
| } | ||||
| 
 | ||||
| // GetElementsForDashboard gets all connected elements for a specific dashboard.
 | ||||
|  |  | |||
|  | @ -230,6 +230,13 @@ type PatchLibraryElementCommand struct { | |||
| 	UID string `json:"uid"` | ||||
| } | ||||
| 
 | ||||
| // GetLibraryElementCommand is the command for getting a library element.
 | ||||
| type GetLibraryElementCommand struct { | ||||
| 	FolderName string | ||||
| 	FolderID   int64 | ||||
| 	UID        string | ||||
| } | ||||
| 
 | ||||
| // SearchLibraryElementsQuery is the query used for searching for Elements
 | ||||
| type SearchLibraryElementsQuery struct { | ||||
| 	PerPage          int | ||||
|  |  | |||
|  | @ -10,21 +10,30 @@ 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/folder" | ||||
| 	"github.com/grafana/grafana/pkg/services/libraryelements" | ||||
| 	"github.com/grafana/grafana/pkg/services/libraryelements/model" | ||||
| 	"github.com/grafana/grafana/pkg/services/store/entity" | ||||
| 	"github.com/grafana/grafana/pkg/services/user" | ||||
| 	"github.com/grafana/grafana/pkg/setting" | ||||
| ) | ||||
| 
 | ||||
| func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.RouteRegister, | ||||
| 	libraryElementService libraryelements.Service) *LibraryPanelService { | ||||
| 	return &LibraryPanelService{ | ||||
| 	libraryElementService libraryelements.Service, folderService folder.Service) (*LibraryPanelService, error) { | ||||
| 	lps := LibraryPanelService{ | ||||
| 		Cfg:                   cfg, | ||||
| 		SQLStore:              sqlStore, | ||||
| 		RouteRegister:         routeRegister, | ||||
| 		LibraryElementService: libraryElementService, | ||||
| 		FolderService:         folderService, | ||||
| 		log:                   log.New("library-panels"), | ||||
| 	} | ||||
| 
 | ||||
| 	if err := folderService.RegisterService(lps); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &lps, nil | ||||
| } | ||||
| 
 | ||||
| // Service is a service for operating on library panels.
 | ||||
|  | @ -44,6 +53,7 @@ type LibraryPanelService struct { | |||
| 	SQLStore              db.DB | ||||
| 	RouteRegister         routing.RouteRegister | ||||
| 	LibraryElementService libraryelements.Service | ||||
| 	FolderService         folder.Service | ||||
| 	log                   log.Logger | ||||
| } | ||||
| 
 | ||||
|  | @ -130,7 +140,7 @@ func importLibraryPanelsRecursively(c context.Context, service libraryelements.S | |||
| 			return errLibraryPanelHeaderUIDMissing | ||||
| 		} | ||||
| 
 | ||||
| 		_, err := service.GetElement(c, signedInUser, UID) | ||||
| 		_, err := service.GetElement(c, signedInUser, model.GetLibraryElementCommand{UID: UID, FolderName: dashboards.RootFolderName}) | ||||
| 		if err == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | @ -171,3 +181,28 @@ func importLibraryPanelsRecursively(c context.Context, service libraryelements.S | |||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // CountInFolder is a handler for retrieving the number of library panels contained
 | ||||
| // within a given folder and for a specific organisation.
 | ||||
| func (lps LibraryPanelService) CountInFolder(ctx context.Context, orgID int64, folderUID string, u *user.SignedInUser) (int64, error) { | ||||
| 	var count int64 | ||||
| 	return count, lps.SQLStore.WithDbSession(ctx, func(sess *db.Session) error { | ||||
| 		folder, err := lps.FolderService.Get(ctx, &folder.GetFolderQuery{UID: &folderUID, OrgID: orgID, SignedInUser: u}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		q := sess.Table("library_element").Where("org_id = ?", u.OrgID). | ||||
| 			Where("folder_id = ?", folder.ID).Where("kind = ?", int64(model.PanelElement)) | ||||
| 		count, err = q.Count() | ||||
| 		return err | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // DeleteInFolder deletes the library panels contained in a given folder.
 | ||||
| func (lps LibraryPanelService) DeleteInFolder(ctx context.Context, orgID int64, folderUID string, user *user.SignedInUser) error { | ||||
| 	return lps.LibraryElementService.DeleteLibraryElementsInFolder(ctx, user, folderUID) | ||||
| } | ||||
| 
 | ||||
| // Kind returns the name of the library panel type of entity.
 | ||||
| func (lps LibraryPanelService) Kind() string { return entity.StandardKindLibraryPanel } | ||||
|  |  | |||
|  | @ -319,6 +319,23 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { | |||
| 			require.Len(t, elements, 1) | ||||
| 			require.Equal(t, sc.initialResult.Result.UID, elements[sc.initialResult.Result.UID].UID) | ||||
| 		}) | ||||
| 
 | ||||
| 	scenarioWithLibraryPanel(t, "It should return the correct count of library panels in a folder", | ||||
| 		func(t *testing.T, sc scenarioContext) { | ||||
| 			count, err := sc.lps.CountInFolder(context.Background(), sc.user.OrgID, sc.folder.UID, sc.user) | ||||
| 			require.NoError(t, err) | ||||
| 			require.Equal(t, int64(1), count) | ||||
| 		}) | ||||
| 
 | ||||
| 	scenarioWithLibraryPanel(t, "It should delete library panels in a folder", | ||||
| 		func(t *testing.T, sc scenarioContext) { | ||||
| 			err := sc.lps.DeleteInFolder(context.Background(), sc.user.OrgID, sc.folder.UID, sc.user) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			_, err = sc.elementService.GetElement(sc.ctx, sc.user, | ||||
| 				model.GetLibraryElementCommand{UID: sc.initialResult.Result.UID, FolderName: sc.folder.Title}) | ||||
| 			require.EqualError(t, err, model.ErrLibraryElementNotFound.Error()) | ||||
| 		}) | ||||
| } | ||||
| 
 | ||||
| func TestImportLibraryPanelsForDashboard(t *testing.T) { | ||||
|  | @ -367,14 +384,16 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { | |||
| 				}, | ||||
| 			} | ||||
| 
 | ||||
| 			_, err := sc.elementService.GetElement(sc.ctx, sc.user, missingUID) | ||||
| 			_, err := sc.elementService.GetElement(sc.ctx, sc.user, | ||||
| 				model.GetLibraryElementCommand{UID: missingUID, FolderName: dashboards.RootFolderName}) | ||||
| 
 | ||||
| 			require.EqualError(t, err, model.ErrLibraryElementNotFound.Error()) | ||||
| 
 | ||||
| 			err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.NewFromAny(libraryElements), panels, 0) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			element, err := sc.elementService.GetElement(sc.ctx, sc.user, missingUID) | ||||
| 			element, err := sc.elementService.GetElement(sc.ctx, sc.user, | ||||
| 				model.GetLibraryElementCommand{UID: missingUID, FolderName: dashboards.RootFolderName}) | ||||
| 			require.NoError(t, err) | ||||
| 			var expected = getExpected(t, element, missingUID, missingName, missingModel) | ||||
| 			var result = toLibraryElement(t, element) | ||||
|  | @ -406,13 +425,15 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { | |||
| 				}, | ||||
| 			} | ||||
| 
 | ||||
| 			_, err := sc.elementService.GetElement(sc.ctx, sc.user, existingUID) | ||||
| 			_, err := sc.elementService.GetElement(sc.ctx, sc.user, | ||||
| 				model.GetLibraryElementCommand{UID: existingUID, FolderName: dashboards.RootFolderName}) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.New(), panels, sc.folder.ID) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			element, err := sc.elementService.GetElement(sc.ctx, sc.user, existingUID) | ||||
| 			element, err := sc.elementService.GetElement(sc.ctx, sc.user, | ||||
| 				model.GetLibraryElementCommand{UID: existingUID, FolderName: dashboards.RootFolderName}) | ||||
| 			require.NoError(t, err) | ||||
| 			var expected = getExpected(t, element, existingUID, existingName, sc.initialResult.Result.Model) | ||||
| 			expected.FolderID = sc.initialResult.Result.FolderID | ||||
|  | @ -519,16 +540,15 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { | |||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
| 
 | ||||
| 			_, err := sc.elementService.GetElement(sc.ctx, sc.user, outsideUID) | ||||
| 			_, err := sc.elementService.GetElement(sc.ctx, sc.user, model.GetLibraryElementCommand{UID: outsideUID, FolderName: dashboards.RootFolderName}) | ||||
| 			require.EqualError(t, err, model.ErrLibraryElementNotFound.Error()) | ||||
| 			_, err = sc.elementService.GetElement(sc.ctx, sc.user, insideUID) | ||||
| 			_, err = sc.elementService.GetElement(sc.ctx, sc.user, model.GetLibraryElementCommand{UID: insideUID, FolderName: dashboards.RootFolderName}) | ||||
| 			require.EqualError(t, err, model.ErrLibraryElementNotFound.Error()) | ||||
| 
 | ||||
| 			err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.NewFromAny(libraryElements), panels, 0) | ||||
| 			require.NoError(t, err) | ||||
| 
 | ||||
| 			element, err := sc.elementService.GetElement(sc.ctx, sc.user, outsideUID) | ||||
| 			element, err := sc.elementService.GetElement(sc.ctx, sc.user, model.GetLibraryElementCommand{UID: outsideUID, FolderName: dashboards.RootFolderName}) | ||||
| 			require.NoError(t, err) | ||||
| 			expected := getExpected(t, element, outsideUID, outsideName, outsideModel) | ||||
| 			result := toLibraryElement(t, element) | ||||
|  | @ -536,7 +556,7 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { | |||
| 				t.Fatalf("Result mismatch (-want +got):\n%s", diff) | ||||
| 			} | ||||
| 
 | ||||
| 			element, err = sc.elementService.GetElement(sc.ctx, sc.user, insideUID) | ||||
| 			element, err = sc.elementService.GetElement(sc.ctx, sc.user, model.GetLibraryElementCommand{UID: insideUID, FolderName: dashboards.RootFolderName}) | ||||
| 			require.NoError(t, err) | ||||
| 			expected = getExpected(t, element, insideUID, insideName, insideModel) | ||||
| 			result = toLibraryElement(t, element) | ||||
|  | @ -607,6 +627,7 @@ type scenarioContext struct { | |||
| 	folder         *folder.Folder | ||||
| 	initialResult  libraryPanelResult | ||||
| 	sqlStore       db.DB | ||||
| 	lps            LibraryPanelService | ||||
| } | ||||
| 
 | ||||
| func toLibraryElement(t *testing.T, res model.LibraryElementDTO) libraryElement { | ||||
|  | @ -814,6 +835,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo | |||
| 			Cfg:                   cfg, | ||||
| 			SQLStore:              sqlStore, | ||||
| 			LibraryElementService: elementService, | ||||
| 			FolderService:         folderService, | ||||
| 		} | ||||
| 
 | ||||
| 		usr := &user.SignedInUser{ | ||||
|  | @ -853,6 +875,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo | |||
| 			service:        &service, | ||||
| 			elementService: elementService, | ||||
| 			sqlStore:       sqlStore, | ||||
| 			lps:            service, | ||||
| 		} | ||||
| 
 | ||||
| 		foldr := createFolder(t, sc, "ScenarioFolder") | ||||
|  |  | |||
|  | @ -586,7 +586,7 @@ func (st DBstore) GetAlertRulesForScheduling(ctx context.Context, query *ngmodel | |||
| } | ||||
| 
 | ||||
| // DeleteInFolder deletes the rules contained in a given folder along with their associated data.
 | ||||
| func (st DBstore) DeleteInFolder(ctx context.Context, orgID int64, folderUID string) error { | ||||
| func (st DBstore) DeleteInFolder(ctx context.Context, orgID int64, folderUID string, user *user.SignedInUser) error { | ||||
| 	rules, err := st.ListAlertRules(ctx, &ngmodels.ListAlertRulesQuery{ | ||||
| 		OrgID:         orgID, | ||||
| 		NamespaceUIDs: []string{folderUID}, | ||||
|  |  | |||
|  | @ -474,7 +474,7 @@ func TestIntegration_DeleteInFolder(t *testing.T) { | |||
| 	} | ||||
| 	rule := createRule(t, store, nil) | ||||
| 
 | ||||
| 	err := store.DeleteInFolder(context.Background(), rule.OrgID, rule.NamespaceUID) | ||||
| 	err := store.DeleteInFolder(context.Background(), rule.OrgID, rule.NamespaceUID, nil) | ||||
| 	require.NoError(t, err) | ||||
| 
 | ||||
| 	c, err := store.CountInFolder(context.Background(), rule.OrgID, rule.NamespaceUID, nil) | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ const ( | |||
| 	// the kind may need to change to better encapsulate { targets:[], transforms:[] }
 | ||||
| 	StandardKindQuery = "query" | ||||
| 
 | ||||
| 	// KindAlertRule is not a real kind. It's used to refer to alert rules, for instance
 | ||||
| 	// StandardKindAlertRule is not a real kind. It's used to refer to alert rules, for instance
 | ||||
| 	// in the folder registry service.
 | ||||
| 	StandardKindAlertRule = "alertrule" | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue