Folders: Reduce DB queries when counting and deleting resources under folders (#81153)

* Add folder store method for fetching all folder descendants

* Modify GetDescendantCounts() to fetch folder descendants at once

* Reduce DB calls when counting library panels under dashboard

* Reduce DB calls when counting dashboards under folder

* Reduce DB calls during folder delete

* Modify folder registry to count/delete entities under multiple folders

* Reduce DB calls when counting

* Reduce DB calls when deleting
This commit is contained in:
Sofia Papagiannaki
2024-01-30 18:26:34 +02:00
committed by GitHub
parent 0139ac205d
commit 89d3b55bec
24 changed files with 335 additions and 411 deletions

View File

@@ -4,6 +4,8 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -188,25 +190,41 @@ func importLibraryPanelsRecursively(c context.Context, service libraryelements.S
// 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 identity.Requester) (int64, error) {
func (lps LibraryPanelService) CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, u identity.Requester) (int64, error) {
if len(folderUIDs) == 0 {
return 0, nil
}
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})
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryPanels).Inc()
// the sequential IDs for the respective entries of dashboard and folder tables are different,
// so we need to get the folder ID from the dashboard table
// TODO: In the future, we should consider adding a folder UID column to the library_element table
// and use that instead of the folder ID.
s := fmt.Sprintf(`SELECT COUNT(*) FROM library_element
WHERE org_id = ? AND folder_id IN (SELECT id FROM dashboard WHERE org_id = ? AND uid IN (%s)) AND kind = ?`, strings.Repeat("?,", len(folderUIDs)-1)+"?")
args := make([]interface{}, 0, len(folderUIDs)+2)
args = append(args, orgID, orgID)
for _, folderUID := range folderUIDs {
args = append(args, folderUID)
}
args = append(args, int64(model.PanelElement))
_, err := sess.SQL(s, args...).Get(&count)
if err != nil {
return err
}
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryPanels).Inc()
// nolint:staticcheck
q := sess.Table("library_element").Where("org_id = ?", u.GetOrgID()).
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 identity.Requester) error {
return lps.LibraryElementService.DeleteLibraryElementsInFolder(ctx, user, folderUID)
func (lps LibraryPanelService) DeleteInFolders(ctx context.Context, orgID int64, folderUIDs []string, user identity.Requester) error {
for _, folderUID := range folderUIDs {
if err := lps.LibraryElementService.DeleteLibraryElementsInFolder(ctx, user, folderUID); err != nil {
return err
}
}
return nil
}
// Kind returns the name of the library panel type of entity.

View File

@@ -326,14 +326,14 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) {
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)
count, err := sc.lps.CountInFolders(context.Background(), sc.user.OrgID, []string{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)
err := sc.lps.DeleteInFolders(context.Background(), sc.user.OrgID, []string{sc.folder.UID}, sc.user)
require.NoError(t, err)
_, err = sc.elementService.GetElement(sc.ctx, sc.user,