@ -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 ) {
origNewGuardian := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true , CanViewValue : true } )
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" ,
} ,
}
ancestorUIDs := CreateSubtreeInStore ( t , nestedFolderStore , serviceWithFlagOn , 3 , "with-force" , createCmd )
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
origNewGuardian := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian {
CanSaveValue : true ,
CanViewValue : true ,
// CanEditValue is required to create library elements
CanEditValue : true ,
} )
parent , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestorUIDs [ 0 ] )
require . NoError ( t , err )
subfolder , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestorUIDs [ 1 ] )
elementService := libraryelements . ProvideService ( cfg , db , routeRegister , tc . service , tc . featuresFlag )
lps , err := librarypanels . ProvideService ( cfg , db , routeRegister , elementService , tc . service )
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 : true ,
}
err = serviceWithFlagOn . Delete ( context . Background ( ) , & deleteCmd )
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
for i , uid := range ancestorUIDs {
// dashboard table
_ , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , uid )
require . ErrorIs ( t , err , dashboards . ErrFolderNotFound )
// folder table
_ , err = serviceWithFlagOn . store . Get ( context . Background ( ) , folder . GetFolderQuery { UID : & ancestorUIDs [ i ] , OrgID : orgID } )
require . ErrorIs ( t , err , folder . ErrFolderNotFound )
}
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 } )
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 , 3 , "without-force" , createCmd )
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" )
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 )
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 )
// folder table
_ , err = serviceWithFlagOn . store . Get ( context . Background ( ) , folder . GetFolderQuery { UID : & ancestorUIDs [ i ] , OrgID : orgID } )
_ = createRule ( t , alertStore , subfolder . UID , "sub alert" )
libraryElementCmd . FolderID = subfolder . ID
subPanel , err = lps . LibraryElementService . CreateElement ( context . Background ( ) , & signedInUser , libraryElementCmd )
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 ] )
libraryElementCmd . FolderID = parent . ID
parentPanel , err := lps . LibraryElementService . CreateElement ( context . Background ( ) , & signedInUser , libraryElementCmd )
require . NoError ( t , err )
_ = createRule ( t , alertStore , parent . UID , "parent alert" )
deleteCmd := folder . DeleteFolderCommand {
UID : ancestorUIDs [ 0 ] ,
OrgID : orgID ,
SignedInUser : & signedInUser ,
ForceDeleteRules : tru e,
ForceDeleteRules : tc . forceDelete ,
}
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" )
err = tc . service . Delete ( context . Background ( ) , & deleteCmd )
require . ErrorIs ( t , err , tc . deletionErr )
deleteCmd := folder . DeleteFolderCommand {
UID : ancestorUIDs [ 0 ] ,
OrgID : orgID ,
SignedInUser : & signedInUser ,
ForceDeleteRules : false ,
for i , uid := range ancestorUIDs {
// dashboard table
_ , err := tc . service . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , uid )
require . ErrorIs ( t , err , tc . dashboardErr )
// folder table
_ , err = tc . service . store . Get ( context . Background ( ) , folder . GetFolderQuery { UID : & ancestorUIDs [ i ] , OrgID : orgID } )
require . ErrorIs ( t , err , tc . folderErr )
}
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 )
_ , 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
for _ , uid := range ancestorUIDs {
err := serviceWithFlagOff . store . Delete ( context . Background ( ) , uid , orgID )
require . NoError ( t , err )
}
} )
} )
} )
}
} )
}