mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
LibraryPanels: Deletes library panels during folder deletion (#31572)
* Refactor: adds permissions for library panel creation * Refactor: checks folder permissions for patch requests * Chore: changes after PR comments * Refactor: adds permissions to delete * Refactor: moves get all permission tests out of get all tests * Chore: move out get all tests to a separate file * Refactor: adds permissions to get handler * Refactor: fixes a bug with getting library panels in General folder * Refactor: adds permissions for connect/disconnect * Refactor: adds permissions and tests for get connected dashboards * Tests: adds tests for connected dashboards in General Folder * LibraryPanels: Deletes library panels during folder deletion * LibraryPanels: Deletes library panels during folder deletion * Update pkg/api/folder.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/librarypanels/librarypanels_permissions_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Chore: updates after PR comments * Chore: forgot to change some function signatures Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
@@ -231,6 +231,59 @@ func (lps *LibraryPanelService) disconnectLibraryPanelsForDashboard(c *models.Re
|
||||
})
|
||||
}
|
||||
|
||||
// deleteLibraryPanelsInFolder deletes all Library Panels for a folder.
|
||||
func (lps *LibraryPanelService) deleteLibraryPanelsInFolder(c *models.ReqContext, folderUID string) error {
|
||||
return lps.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
|
||||
var folderUIDs []struct {
|
||||
ID int64 `xorm:"id"`
|
||||
}
|
||||
err := session.SQL("SELECT id from dashboard WHERE uid=? AND org_id=? AND is_folder=1", folderUID, c.SignedInUser.OrgId).Find(&folderUIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(folderUIDs) != 1 {
|
||||
return fmt.Errorf("found %d folders, while expecting at most one", len(folderUIDs))
|
||||
}
|
||||
folderID := folderUIDs[0].ID
|
||||
|
||||
if err := requirePermissionsOnFolder(c.SignedInUser, folderID); err != nil {
|
||||
return err
|
||||
}
|
||||
var dashIDs []struct {
|
||||
DashboardID int64 `xorm:"dashboard_id"`
|
||||
}
|
||||
sql := "SELECT lpd.dashboard_id FROM library_panel AS lp"
|
||||
sql += " INNER JOIN library_panel_dashboard lpd on lp.id = lpd.librarypanel_id"
|
||||
sql += " WHERE lp.folder_id=? AND lp.org_id=?"
|
||||
err = session.SQL(sql, folderID, c.SignedInUser.OrgId).Find(&dashIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dashIDs) > 0 {
|
||||
return ErrFolderHasConnectedLibraryPanels
|
||||
}
|
||||
|
||||
var panelIDs []struct {
|
||||
ID int64 `xorm:"id"`
|
||||
}
|
||||
err = session.SQL("SELECT id from library_panel WHERE folder_id=? AND org_id=?", folderID, c.SignedInUser.OrgId).Find(&panelIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, panelID := range panelIDs {
|
||||
_, err := session.Exec("DELETE FROM library_panel_dashboard WHERE librarypanel_id=?", panelID.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := session.Exec("DELETE FROM library_panel WHERE folder_id=? AND org_id=?", folderID, c.SignedInUser.OrgId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func getLibraryPanel(session *sqlstore.DBSession, uid string, orgID int64) (LibraryPanelWithMeta, error) {
|
||||
libraryPanels := make([]LibraryPanelWithMeta, 0)
|
||||
sql := sqlStatmentLibrayPanelDTOWithMeta + "WHERE lp.uid=? AND lp.org_id=?"
|
||||
|
||||
@@ -219,6 +219,13 @@ func (lps *LibraryPanelService) DisconnectLibraryPanelsForDashboard(c *models.Re
|
||||
return lps.disconnectLibraryPanelsForDashboard(c, dash.Id, panelCount)
|
||||
}
|
||||
|
||||
func (lps *LibraryPanelService) DeleteLibraryPanelsInFolder(c *models.ReqContext, folderUID string) error {
|
||||
if !lps.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
return lps.deleteLibraryPanelsInFolder(c, folderUID)
|
||||
}
|
||||
|
||||
// AddMigration defines database migrations.
|
||||
// If Panel Library is not enabled does nothing.
|
||||
func (lps *LibraryPanelService) AddMigration(mg *migrator.Migrator) {
|
||||
|
||||
@@ -149,6 +149,25 @@ func TestLibraryPanelPermissions(t *testing.T) {
|
||||
resp = sc.service.disconnectHandler(sc.reqContext)
|
||||
require.Equal(t, testCase.status, resp.Status())
|
||||
})
|
||||
|
||||
testScenario(t, fmt.Sprintf("When %s tries to delete all library panels in a folder with %s, it should return correct status", testCase.role, testCase.desc),
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items)
|
||||
cmd := getCreateCommand(folder.Id, "Library Panel Name")
|
||||
resp := sc.service.createHandler(sc.reqContext, cmd)
|
||||
validateAndUnMarshalResponse(t, resp)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
err := sc.service.DeleteLibraryPanelsInFolder(sc.reqContext, folder.Uid)
|
||||
switch testCase.status {
|
||||
case 200:
|
||||
require.NoError(t, err)
|
||||
case 403:
|
||||
require.EqualError(t, err, models.ErrFolderAccessDenied.Error())
|
||||
default:
|
||||
t.Fatalf("Unrecognized test case status %d", testCase.status)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var generalFolderCases = []struct {
|
||||
|
||||
@@ -630,6 +630,38 @@ func TestDisconnectLibraryPanelsForDashboard(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteLibraryPanelsInFolder(t *testing.T) {
|
||||
scenarioWithLibraryPanel(t, "When an admin tries to delete a folder that contains connected library panels, it should fail",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
|
||||
resp := sc.service.connectHandler(sc.reqContext)
|
||||
require.Equal(t, 200, resp.Status())
|
||||
|
||||
err := sc.service.DeleteLibraryPanelsInFolder(sc.reqContext, sc.folder.Uid)
|
||||
require.EqualError(t, err, ErrFolderHasConnectedLibraryPanels.Error())
|
||||
})
|
||||
|
||||
scenarioWithLibraryPanel(t, "When an admin tries to delete a folder that contains disconnected library panels, it should delete all disconnected library panels too",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
resp := sc.service.getAllHandler(sc.reqContext)
|
||||
require.Equal(t, 200, resp.Status())
|
||||
var result libraryPanelsResult
|
||||
err := json.Unmarshal(resp.Body(), &result)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result.Result)
|
||||
require.Equal(t, 1, len(result.Result))
|
||||
|
||||
err = sc.service.DeleteLibraryPanelsInFolder(sc.reqContext, sc.folder.Uid)
|
||||
require.NoError(t, err)
|
||||
resp = sc.service.getAllHandler(sc.reqContext)
|
||||
require.Equal(t, 200, resp.Status())
|
||||
err = json.Unmarshal(resp.Body(), &result)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result.Result)
|
||||
require.Equal(t, 0, len(result.Result))
|
||||
})
|
||||
}
|
||||
|
||||
type libraryPanel struct {
|
||||
ID int64 `json:"id"`
|
||||
OrgID int64 `json:"orgId"`
|
||||
|
||||
@@ -96,6 +96,8 @@ var (
|
||||
errLibraryPanelHeaderUIDMissing = errors.New("library panel header is missing required property uid")
|
||||
// errLibraryPanelHeaderNameMissing is an error for when a library panel header is missing the name property.
|
||||
errLibraryPanelHeaderNameMissing = errors.New("library panel header is missing required property name")
|
||||
// ErrFolderHasConnectedLibraryPanels is an error for when an user deletes a folder that contains connected library panels.
|
||||
ErrFolderHasConnectedLibraryPanels = errors.New("folder contains library panels that are linked to dashboards")
|
||||
)
|
||||
|
||||
// Commands
|
||||
|
||||
Reference in New Issue
Block a user