Dashboards: Refactor service to make it injectable by wire (#44588)

* Add providers to folder and dashboard services

* Refactor folder and dashboard services

* Move store implementation to its own file due wire cannot allow us to cast to SQLStore

* Add store in some places and more missing dependencies

* Bad merge fix

* Remove old functions from tests and few fixes

* Fix provisioning

* Remove store from http server and some test fixes

* Test fixes

* Fix dashboard and folder tests

* Fix library tests

* Fix provisioning tests

* Fix plugins manager tests

* Fix alert and org users tests

* Refactor service package and more test fixes

* Fix dashboard_test tets

* Fix api tests

* Some lint fixes

* Fix lint

* More lint :/

* Move dashboard integration tests to dashboards service and fix dependencies

* Lint + tests

* More integration tests fixes

* Lint

* Lint again

* Fix tests again and again anda again

* Update searchstore_test

* Fix goimports

* More go imports

* More imports fixes

* Fix lint

* Move UnprovisionDashboard function into dashboard service and remove bus

* Use search service instead of bus

* Fix test

* Fix go imports

* Use nil in tests
This commit is contained in:
Selene 2022-02-16 14:15:44 +01:00 committed by GitHub
parent 4393992775
commit d5b98772ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 2377 additions and 1844 deletions

View File

@ -1,15 +0,0 @@
package api
import (
"context"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
)
// updateDashboardACL updates a dashboard's ACL items.
//
// Stubbable by tests.
var updateDashboardACL = func(ctx context.Context, s dashboards.Store, dashID int64, items []*models.DashboardAcl) error {
return s.UpdateDashboardACLCtx(ctx, dashID, items)
}

View File

@ -28,6 +28,9 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/resourceservices"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/dashboards"
dashboardsstore "github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/quota"
@ -276,6 +279,8 @@ type accessControlScenarioContext struct {
// cfg is the setting provider
cfg *setting.Cfg
dashboardsStore dashboards.Store
}
func setAccessControlPermissions(acmock *accesscontrolmock.Mock, perms []*accesscontrol.Permission, org int64) {
@ -347,6 +352,8 @@ func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessCont
bus := bus.GetBus()
dashboardsStore := dashboardsstore.ProvideDashboardStore(db)
routeRegister := routing.NewRouteRegister()
// Create minimal HTTP Server
hs := &HTTPServer{
@ -358,6 +365,7 @@ func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessCont
RouteRegister: routeRegister,
SQLStore: db,
searchUsersService: searchusers.ProvideUsersService(bus, filters.ProvideOSSSearchUserFilter()),
dashboardService: dashboardservice.ProvideDashboardService(dashboardsStore),
}
// Defining the accesscontrol service has to be done before registering routes
@ -402,12 +410,13 @@ func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessCont
hs.RouteRegister.Register(m.Router)
return accessControlScenarioContext{
server: m,
initCtx: initCtx,
hs: hs,
acmock: acmock,
db: db,
cfg: cfg,
server: m,
initCtx: initCtx,
hs: hs,
acmock: acmock,
db: db,
cfg: cfg,
dashboardsStore: dashboardsStore,
}
}

View File

@ -148,8 +148,7 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
meta.FolderUrl = query.Result.GetUrl()
}
svc := dashboards.NewProvisioningService(hs.SQLStore)
provisioningData, err := svc.GetProvisionedDashboardDataByDashboardID(dash.Id)
provisioningData, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardID(dash.Id)
if err != nil {
return response.Error(500, "Error while checking if dashboard is provisioned", err)
}
@ -233,8 +232,8 @@ func (hs *HTTPServer) deleteDashboard(c *models.ReqContext) response.Response {
if err != nil {
hs.log.Error("Failed to disconnect library elements", "dashboard", dash.Id, "user", c.SignedInUser.UserId, "error", err)
}
svc := dashboards.NewService(hs.SQLStore)
err = svc.DeleteDashboard(c.Req.Context(), dash.Id, c.OrgId)
err = hs.dashboardService.DeleteDashboard(c.Req.Context(), dash.Id, c.OrgId)
if err != nil {
var dashboardErr models.DashboardErr
if ok := errors.As(err, &dashboardErr); ok {
@ -271,8 +270,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
cmd.OrgId = c.OrgId
cmd.UserId = c.UserId
if cmd.FolderUid != "" {
folders := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := folders.GetFolderByUID(ctx, cmd.FolderUid)
folder, err := hs.folderService.GetFolderByUID(ctx, c.SignedInUser, c.OrgId, cmd.FolderUid)
if err != nil {
if errors.Is(err, models.ErrFolderNotFound) {
return response.Error(400, "Folder not found", err)
@ -294,18 +292,17 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
}
}
svc := dashboards.NewProvisioningService(hs.SQLStore)
var provisioningData *models.DashboardProvisioning
if dash.Id != 0 {
data, err := svc.GetProvisionedDashboardDataByDashboardID(dash.Id)
data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardID(dash.Id)
if err != nil {
return response.Error(500, "Error while checking if dashboard is provisioned using ID", err)
}
provisioningData = data
} else if dash.Uid != "" {
data, err := svc.GetProvisionedDashboardDataByDashboardUID(dash.OrgId, dash.Uid)
if err != nil && (!errors.Is(err, models.ErrProvisionedDashboardNotFound) && !errors.Is(err, models.ErrDashboardNotFound)) {
return response.Error(500, "Error while checking if dashboard is provisioned using UID", err)
data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardUID(dash.OrgId, dash.Uid)
if err != nil && !errors.Is(err, models.ErrProvisionedDashboardNotFound) && !errors.Is(err, models.ErrDashboardNotFound) {
return response.Error(500, "Error while checking if dashboard is provisioned", err)
}
provisioningData = data
}
@ -329,8 +326,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
Overwrite: cmd.Overwrite,
}
dashSvc := dashboards.NewService(hs.SQLStore)
dashboard, err := dashSvc.SaveDashboard(alerting.WithUAEnabled(ctx, hs.Cfg.UnifiedAlerting.IsEnabled()), dashItem, allowUiUpdate)
dashboard, err := hs.dashboardService.SaveDashboard(alerting.WithUAEnabled(ctx, hs.Cfg.UnifiedAlerting.IsEnabled()), dashItem, allowUiUpdate)
if hs.Live != nil {
// Tell everyone listening that the dashboard changed
@ -362,7 +358,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
if hs.Cfg.EditorsCanAdmin && newDashboard {
inFolder := cmd.FolderId > 0
err := dashSvc.MakeUserAdmin(ctx, cmd.OrgId, cmd.UserId, dashboard.Id, !inFolder)
err := hs.dashboardService.MakeUserAdmin(ctx, cmd.OrgId, cmd.UserId, dashboard.Id, !inFolder)
if err != nil {
hs.log.Error("Could not make user admin", "dashboard", dashboard.Title, "user", c.SignedInUser.UserId, "error", err)
}

View File

@ -112,7 +112,7 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response.
return response.Error(403, "Cannot remove own admin permission for a folder", nil)
}
if err := updateDashboardACL(c.Req.Context(), hs.SQLStore, dashID, items); err != nil {
if err := hs.dashboardService.UpdateDashboardACL(c.Req.Context(), dashID, items); err != nil {
if errors.Is(err, models.ErrDashboardAclInfoMissing) ||
errors.Is(err, models.ErrDashboardPermissionDashboardEmpty) {
return response.Error(409, err.Error(), err)

View File

@ -1,59 +1,38 @@
package api
import (
"context"
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestDashboardPermissionAPIEndpoint(t *testing.T) {
t.Run("Dashboard permissions test", func(t *testing.T) {
settings := setting.NewCfg()
dashboardStore := &database.FakeDashboardStore{}
defer dashboardStore.AssertExpectations(t)
mockSQLStore := mockstore.NewSQLStoreMock()
hs := &HTTPServer{
Cfg: settings,
SQLStore: mockSQLStore,
Cfg: settings,
dashboardService: dashboardservice.ProvideDashboardService(dashboardStore),
SQLStore: mockSQLStore,
}
t.Run("Given dashboard not exists", func(t *testing.T) {
mockSQLStore.ExpectedError = models.ErrDashboardNotFound
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions",
"/api/dashboards/id/:dashboardId/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
callGetDashboardPermissions(sc, hs)
assert.Equal(t, 404, sc.resp.Code)
}, mockSQLStore)
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserID: 1000, Permission: models.PERMISSION_ADMIN},
},
}
updateDashboardPermissionScenario(t, updatePermissionContext{
desc: "When calling POST on",
url: "/api/dashboards/id/1/permissions",
routePattern: "/api/dashboards/id/:dashboardId/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
callUpdateDashboardPermissions(t, sc)
assert.Equal(t, 404, sc.resp.Code)
},
}, hs)
})
t.Run("Given user has no admin permissions", func(t *testing.T) {
origNewGuardian := guardian.New
t.Cleanup(func() {
@ -63,7 +42,6 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
getDashboardQueryResult := models.NewDashboard("Dash")
mockSQLStore := mockstore.NewSQLStoreMock()
mockSQLStore.ExpectedDashboard = getDashboardQueryResult
mockSQLStore.ExpectedError = nil
@ -80,6 +58,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
},
}
dashboardStore.On("UpdateDashboardACL", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
updateDashboardPermissionScenario(t, updatePermissionContext{
desc: "When calling POST on",
url: "/api/dashboards/id/1/permissions",
@ -324,26 +303,20 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
}
assert.Len(t, cmd.Items, 3)
var numOfItems []*models.DashboardAcl
dashboardStore.On("UpdateDashboardACL", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
items := args.Get(2).([]*models.DashboardAcl)
numOfItems = items
}).Return(nil).Once()
updateDashboardPermissionScenario(t, updatePermissionContext{
desc: "When calling POST on",
url: "/api/dashboards/id/1/permissions",
routePattern: "/api/dashboards/id/:dashboardId/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
// TODO: Replace this fake with a fake SQLStore instead (once we can use an interface in its stead)
origUpdateDashboardACL := updateDashboardACL
t.Cleanup(func() {
updateDashboardACL = origUpdateDashboardACL
})
var gotItems []*models.DashboardAcl
updateDashboardACL = func(_ context.Context, _ dashboards.Store, folderID int64, items []*models.DashboardAcl) error {
gotItems = items
return nil
}
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
assert.Len(t, gotItems, 4)
assert.Len(t, numOfItems, 4)
},
}, hs)
})
@ -357,15 +330,6 @@ func callGetDashboardPermissions(sc *scenarioContext, hs *HTTPServer) {
func callUpdateDashboardPermissions(t *testing.T, sc *scenarioContext) {
t.Helper()
origUpdateDashboardACL := updateDashboardACL
t.Cleanup(func() {
updateDashboardACL = origUpdateDashboardACL
})
updateDashboardACL = func(_ context.Context, _ dashboards.Store, dashID int64, items []*models.DashboardAcl) error {
return nil
}
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}

View File

@ -7,7 +7,6 @@ import (
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"testing"
"github.com/grafana/grafana/pkg/api/dtos"
@ -15,11 +14,12 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
service "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/live"
@ -30,6 +30,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
@ -145,7 +146,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUp()
sc.sqlStore = mockSQLStore
dash := getDashboardShouldReturn200(sc)
dash := getDashboardShouldReturn200(t, sc)
assert.False(t, dash.Meta.CanEdit)
assert.False(t, dash.Meta.CanSave)
@ -177,7 +178,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUp()
sc.sqlStore = mockSQLStore
dash := getDashboardShouldReturn200(sc)
dash := getDashboardShouldReturn200(t, sc)
assert.True(t, dash.Meta.CanEdit)
assert.True(t, dash.Meta.CanSave)
@ -212,11 +213,13 @@ func TestDashboardAPIEndpoint(t *testing.T) {
mockSQLStore := mockstore.NewSQLStoreMock()
mockSQLStore.ExpectedDashboard = fakeDash
dashboardStore := database.ProvideDashboardStore(sqlstore.InitTestDB(t))
hs := &HTTPServer{
Cfg: setting.NewCfg(),
Live: newTestLive(t),
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
dashboardService: service.ProvideDashboardService(dashboardStore),
SQLStore: mockSQLStore,
}
hs.SQLStore = mockSQLStore
@ -345,7 +348,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUpInner()
sc.sqlStore = mockSQLStore
dash := getDashboardShouldReturn200(sc)
dash := getDashboardShouldReturn200(t, sc)
assert.True(t, dash.Meta.CanEdit)
assert.True(t, dash.Meta.CanSave)
@ -412,7 +415,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
require.True(t, setting.ViewersCanEdit)
sc.sqlStore = mockSQLStore
dash := getDashboardShouldReturn200(sc)
dash := getDashboardShouldReturn200(t, sc)
assert.True(t, dash.Meta.CanEdit)
assert.False(t, dash.Meta.CanSave)
@ -445,7 +448,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUpInner()
sc.sqlStore = mockSQLStore
dash := getDashboardShouldReturn200(sc)
dash := getDashboardShouldReturn200(t, sc)
assert.True(t, dash.Meta.CanEdit)
assert.True(t, dash.Meta.CanSave)
@ -493,7 +496,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUpInner()
sc.sqlStore = mockSQLStore
dash := getDashboardShouldReturn200(sc)
dash := getDashboardShouldReturn200(t, sc)
assert.False(t, dash.Meta.CanEdit)
assert.False(t, dash.Meta.CanSave)
@ -535,6 +538,8 @@ func TestDashboardAPIEndpoint(t *testing.T) {
})
t.Run("Post dashboard response tests", func(t *testing.T) {
dashboardStore := &database.FakeDashboardStore{}
defer dashboardStore.AssertExpectations(t)
// This tests that a valid request returns correct response
t.Run("Given a correct request for creating a dashboard", func(t *testing.T) {
const folderID int64 = 3
@ -562,7 +567,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
},
}
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", mock, nil, cmd, func(sc *scenarioContext) {
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, mock, nil, func(sc *scenarioContext) {
callPostDashboardShouldReturnSuccess(sc)
dto := mock.SavedDashboards[0]
@ -612,7 +617,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
GetFolderByUIDResult: &models.Folder{Id: 1, Uid: "folderUID", Title: "Folder"},
}
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", mock, mockFolder, cmd, func(sc *scenarioContext) {
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, mock, mockFolder, func(sc *scenarioContext) {
callPostDashboardShouldReturnSuccess(sc)
dto := mock.SavedDashboards[0]
@ -661,7 +666,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
GetFolderByUIDError: errors.New("Error while searching Folder ID"),
}
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", mock, mockFolder, cmd, func(sc *scenarioContext) {
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", cmd, mock, mockFolder, func(sc *scenarioContext) {
callPostDashboard(sc)
assert.Equal(t, 500, sc.resp.Code)
})
@ -706,7 +711,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
}
postDashboardScenario(t, fmt.Sprintf("Expect '%s' error when calling POST on", tc.SaveError.Error()),
"/api/dashboards", "/api/dashboards", mock, nil, cmd, func(sc *scenarioContext) {
"/api/dashboards", "/api/dashboards", cmd, mock, nil, func(sc *scenarioContext) {
callPostDashboard(sc)
assert.Equal(t, tc.ExpectedStatusCode, sc.resp.Code)
})
@ -852,14 +857,6 @@ func TestDashboardAPIEndpoint(t *testing.T) {
t.Run("Given provisioned dashboard", func(t *testing.T) {
setUp := func() {
origGetProvisionedData := dashboards.GetProvisionedData
t.Cleanup(func() {
dashboards.GetProvisionedData = origGetProvisionedData
})
dashboards.GetProvisionedData = func(dboards.Store, int64) (*models.DashboardProvisioning, error) {
return &models.DashboardProvisioning{ExternalId: "/tmp/grafana/dashboards/test/dashboard1.json"}, nil
}
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
query.Result = []*models.DashboardAclInfoDTO{
{OrgId: testOrgID, DashboardId: 1, UserId: testUserID, Permission: models.PERMISSION_EDIT},
@ -867,6 +864,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
return nil
})
}
mockSQLStore := mockstore.NewSQLStoreMock()
dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "editable": true, "style": "dark"}`))
require.NoError(t, err)
@ -874,37 +872,39 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "editable": true, "style": "dark"}`))
require.NoError(t, err)
mockSQLStore.ExpectedDashboard = &models.Dashboard{Id: 1, Data: dataValue}
sc.sqlStore = mockSQLStore
mock := provisioning.NewProvisioningServiceMock(context.Background())
mock.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
fakeProvisioningService := provisioning.NewProvisioningServiceMock(context.Background())
fakeProvisioningService.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
return "/tmp/grafana/dashboards"
}
dash := getDashboardShouldReturn200WithConfig(sc, mock)
dashboardStore := &database.FakeDashboardStore{}
defer dashboardStore.AssertExpectations(t)
assert.Equal(t, filepath.Join("test", "dashboard1.json"), dash.Meta.ProvisionedExternalId)
dashboardStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{ExternalId: "/dashboard1.json"}, nil).Once()
dash := getDashboardShouldReturn200WithConfig(t, sc, fakeProvisioningService, dashboardStore)
assert.Equal(t, "../../../dashboard1.json", dash.Meta.ProvisionedExternalId, mockSQLStore)
}, mockSQLStore)
loggedInUserScenarioWithRole(t, "When allowUiUpdates is true and calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
mock := provisioning.NewProvisioningServiceMock(context.Background())
mock.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
fakeProvisioningService := provisioning.NewProvisioningServiceMock(context.Background())
fakeProvisioningService.GetDashboardProvisionerResolvedPathFunc = func(name string) string {
return "/tmp/grafana/dashboards"
}
mock.GetAllowUIUpdatesFromConfigFunc = func(name string) bool {
fakeProvisioningService.GetAllowUIUpdatesFromConfigFunc = func(name string) bool {
return true
}
hs := &HTTPServer{
Cfg: setting.NewCfg(),
ProvisioningService: mock,
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
SQLStore: mockSQLStore,
Cfg: setting.NewCfg(),
ProvisioningService: fakeProvisioningService,
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
dashboardProvisioningService: mockDashboardProvisioningService{},
SQLStore: mockSQLStore,
}
hs.callGetDashboard(sc)
@ -919,21 +919,28 @@ func TestDashboardAPIEndpoint(t *testing.T) {
})
}
func getDashboardShouldReturn200WithConfig(sc *scenarioContext, provisioningService provisioning.ProvisioningService) dtos.
DashboardFullWithMeta {
func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, provisioningService provisioning.ProvisioningService, dashboardStore dashboards.Store) dtos.DashboardFullWithMeta {
t.Helper()
if provisioningService == nil {
provisioningService = provisioning.NewProvisioningServiceMock(context.Background())
}
if dashboardStore == nil {
sql := sqlstore.InitTestDB(t)
dashboardStore = database.ProvideDashboardStore(sql)
}
libraryPanelsService := mockLibraryPanelService{}
libraryElementsService := mockLibraryElementService{}
hs := &HTTPServer{
Cfg: setting.NewCfg(),
LibraryPanelService: &libraryPanelsService,
LibraryElementService: &libraryElementsService,
ProvisioningService: provisioningService,
SQLStore: sc.sqlStore,
Cfg: setting.NewCfg(),
LibraryPanelService: &libraryPanelsService,
LibraryElementService: &libraryElementsService,
ProvisioningService: provisioningService,
dashboardProvisioningService: service.ProvideDashboardService(dashboardStore),
SQLStore: sc.sqlStore,
}
hs.callGetDashboard(sc)
@ -947,8 +954,8 @@ func getDashboardShouldReturn200WithConfig(sc *scenarioContext, provisioningServ
return dash
}
func getDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
return getDashboardShouldReturn200WithConfig(sc, nil)
func getDashboardShouldReturn200(t *testing.T, sc *scenarioContext) dtos.DashboardFullWithMeta {
return getDashboardShouldReturn200WithConfig(t, sc, nil, nil)
}
func (hs *HTTPServer) callGetDashboard(sc *scenarioContext) {
@ -976,17 +983,13 @@ func (hs *HTTPServer) callGetDashboardVersions(sc *scenarioContext) {
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
}
func (hs *HTTPServer) callDeleteDashboardByUID(t *testing.T, sc *scenarioContext, mockDashboard *dashboards.FakeDashboardService) {
func (hs *HTTPServer) callDeleteDashboardByUID(t *testing.T,
sc *scenarioContext, mockDashboard *dashboards.FakeDashboardService) {
bus.AddHandler("test", func(ctx context.Context, cmd *models.DeleteDashboardCommand) error {
return nil
})
origNewDashboardService := dashboards.NewService
t.Cleanup(func() {
dashboards.NewService = origNewDashboardService
})
dashboards.MockDashboardService(mockDashboard)
hs.dashboardService = mockDashboard
sc.handlerFunc = hs.DeleteDashboardByUID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
}
@ -1005,9 +1008,7 @@ func callPostDashboardShouldReturnSuccess(sc *scenarioContext) {
assert.Equal(sc.t, 200, sc.resp.Code)
}
func postDashboardScenario(t *testing.T, desc string, url string, routePattern string,
mock *dashboards.FakeDashboardService, mockFolder *fakeFolderService, cmd models.SaveDashboardCommand,
fn scenarioFunc) {
func postDashboardScenario(t *testing.T, desc string, url string, routePattern string, cmd models.SaveDashboardCommand, dashboardService dashboards.DashboardService, folderService dashboards.FolderService, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
@ -1023,6 +1024,8 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
pluginStore: &fakePluginStore{},
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
dashboardService: dashboardService,
folderService: folderService,
}
sc := setupScenarioContext(t, url)
@ -1035,20 +1038,6 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
return hs.PostDashboard(c)
})
origNewDashboardService := dashboards.NewService
origProvisioningService := dashboards.NewProvisioningService
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
dashboards.NewService = origNewDashboardService
dashboards.NewProvisioningService = origProvisioningService
dashboards.NewFolderService = origNewFolderService
})
dashboards.MockDashboardService(mock)
dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
return mockDashboardProvisioningService{}
}
mockFolderService(mockFolder)
sc.m.Post(routePattern, sc.defaultHandler)
fn(sc)
@ -1091,9 +1080,7 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string
})
}
func restoreDashboardVersionScenario(t *testing.T, desc string, url string, routePattern string,
mock *dashboards.FakeDashboardService, cmd dtos.RestoreDashboardVersionCommand, fn scenarioFunc,
sqlStore sqlstore.Store) {
func restoreDashboardVersionScenario(t *testing.T, desc string, url string, routePattern string, mock *dashboards.FakeDashboardService, cmd dtos.RestoreDashboardVersionCommand, fn scenarioFunc, sqlStore sqlstore.Store) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
@ -1107,6 +1094,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
QuotaService: &quota.QuotaService{Cfg: cfg},
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
dashboardService: mock,
SQLStore: sqlStore,
}
@ -1125,17 +1113,6 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
return hs.RestoreDashboardVersion(c)
})
origProvisioningService := dashboards.NewProvisioningService
origNewDashboardService := dashboards.NewService
t.Cleanup(func() {
dashboards.NewService = origNewDashboardService
dashboards.NewProvisioningService = origProvisioningService
})
dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
return mockDashboardProvisioningService{}
}
dashboards.MockDashboardService(mock)
sc.m.Post(routePattern, sc.defaultHandler)
fn(sc)

View File

@ -11,7 +11,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/util"
@ -19,8 +18,7 @@ import (
)
func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folders, err := s.GetFolders(c.Req.Context(), c.QueryInt64("limit"), c.QueryInt64("page"))
folders, err := hs.folderService.GetFolders(c.Req.Context(), c.SignedInUser, c.OrgId, c.QueryInt64("limit"), c.QueryInt64("page"))
if err != nil {
return apierrors.ToFolderErrorResponse(err)
@ -40,8 +38,7 @@ func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response {
}
func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.GetFolderByUID(c.Req.Context(), web.Params(c.Req)[":uid"])
folder, err := hs.folderService.GetFolderByUID(c.Req.Context(), c.SignedInUser, c.OrgId, web.Params(c.Req)[":uid"])
if err != nil {
return apierrors.ToFolderErrorResponse(err)
}
@ -51,14 +48,11 @@ func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response {
}
func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
id, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "id is invalid", err)
}
folder, err := s.GetFolderByID(c.Req.Context(), id)
folder, err := hs.folderService.GetFolderByID(c.Req.Context(), c.SignedInUser, c.OrgId, id)
if err != nil {
return apierrors.ToFolderErrorResponse(err)
}
@ -72,14 +66,13 @@ func (hs *HTTPServer) CreateFolder(c *models.ReqContext) response.Response {
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.CreateFolder(c.Req.Context(), cmd.Title, cmd.Uid)
folder, err := hs.folderService.CreateFolder(c.Req.Context(), c.SignedInUser, c.OrgId, cmd.Title, cmd.Uid)
if err != nil {
return apierrors.ToFolderErrorResponse(err)
}
if hs.Cfg.EditorsCanAdmin {
if err := s.MakeUserAdmin(c.Req.Context(), c.OrgId, c.SignedInUser.UserId, folder.Id, true); err != nil {
if err := hs.folderService.MakeUserAdmin(c.Req.Context(), c.OrgId, c.SignedInUser.UserId, folder.Id, true); err != nil {
hs.log.Error("Could not make user admin", "folder", folder.Title, "user",
c.SignedInUser.UserId, "error", err)
}
@ -94,8 +87,7 @@ func (hs *HTTPServer) UpdateFolder(c *models.ReqContext) response.Response {
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
err := s.UpdateFolder(c.Req.Context(), web.Params(c.Req)[":uid"], &cmd)
err := hs.folderService.UpdateFolder(c.Req.Context(), c.SignedInUser, c.OrgId, web.Params(c.Req)[":uid"], &cmd)
if err != nil {
return apierrors.ToFolderErrorResponse(err)
}
@ -105,7 +97,6 @@ func (hs *HTTPServer) UpdateFolder(c *models.ReqContext) response.Response {
}
func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { // temporarily adding this function to HTTPServer, will be removed from HTTPServer when librarypanels featuretoggle is removed
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
err := hs.LibraryElementService.DeleteLibraryElementsInFolder(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":uid"])
if err != nil {
if errors.Is(err, libraryelements.ErrFolderHasConnectedLibraryElements) {
@ -114,7 +105,7 @@ func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { //
return apierrors.ToFolderErrorResponse(err)
}
f, err := s.DeleteFolder(c.Req.Context(), web.Params(c.Req)[":uid"], c.QueryBool("forceDeleteRules"))
f, err := hs.folderService.DeleteFolder(c.Req.Context(), c.SignedInUser, c.OrgId, web.Params(c.Req)[":uid"], c.QueryBool("forceDeleteRules"))
if err != nil {
return apierrors.ToFolderErrorResponse(err)
}

View File

@ -9,15 +9,13 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
)
func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.GetFolderByUID(c.Req.Context(), web.Params(c.Req)[":uid"])
folder, err := hs.folderService.GetFolderByUID(c.Req.Context(), c.SignedInUser, c.OrgId, web.Params(c.Req)[":uid"])
if err != nil {
return apierrors.ToFolderErrorResponse(err)
@ -68,8 +66,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
return response.Error(400, err.Error(), err)
}
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
folder, err := s.GetFolderByUID(c.Req.Context(), web.Params(c.Req)[":uid"])
folder, err := hs.folderService.GetFolderByUID(c.Req.Context(), c.SignedInUser, c.OrgId, web.Params(c.Req)[":uid"])
if err != nil {
return apierrors.ToFolderErrorResponse(err)
}
@ -117,7 +114,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
return response.Error(403, "Cannot remove own admin permission for a folder", nil)
}
if err := updateDashboardACL(c.Req.Context(), hs.SQLStore, folder.Id, items); err != nil {
if err := hs.dashboardService.UpdateDashboardACL(c.Req.Context(), folder.Id, items); err != nil {
if errors.Is(err, models.ErrDashboardAclInfoMissing) {
err = models.ErrFolderAclInfoMissing
}

View File

@ -1,7 +1,6 @@
package api
import (
"context"
"encoding/json"
"fmt"
"testing"
@ -13,28 +12,28 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
dashboardifaces "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
service "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/mock"
)
func TestFolderPermissionAPIEndpoint(t *testing.T) {
settings := setting.NewCfg()
hs := &HTTPServer{Cfg: settings}
folderService := &dashboards.FakeFolderService{}
defer folderService.AssertExpectations(t)
dashboardStore := &database.FakeDashboardStore{}
defer dashboardStore.AssertExpectations(t)
hs := &HTTPServer{Cfg: settings, folderService: folderService, dashboardService: service.ProvideDashboardService(dashboardStore)}
t.Run("Given folder not exists", func(t *testing.T) {
mock := &fakeFolderService{
GetFolderByUIDError: models.ErrFolderNotFound,
}
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
dashboards.NewFolderService = origNewFolderService
})
mockFolderService(mock)
folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrFolderNotFound).Twice()
mockSQLStore := mockstore.NewSQLStoreMock()
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
callGetFolderPermissions(sc, hs)
@ -61,24 +60,14 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
t.Run("Given user has no admin permissions", func(t *testing.T) {
origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
mock := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
},
}
mockFolderService(mock)
folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrFolderAccessDenied).Twice()
mockSQLStore := mockstore.NewSQLStoreMock()
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
callGetFolderPermissions(sc, hs)
assert.Equal(t, 403, sc.resp.Code)
@ -104,10 +93,8 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
t.Run("Given user has admin permissions and permissions to update", func(t *testing.T) {
origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
@ -122,16 +109,11 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
},
})
mock := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
},
}
mockFolderService(mock)
folderResponse := &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(folderResponse, nil).Twice()
dashboardStore.On("UpdateDashboardACL", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
mockSQLStore := mockstore.NewSQLStoreMock()
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) {
callGetFolderPermissions(sc, hs)
assert.Equal(t, 200, sc.resp.Code)
@ -175,10 +157,8 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
t.Run("When trying to update permissions with duplicate permissions", func(t *testing.T) {
origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
@ -187,15 +167,8 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists,
})
mock := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
},
}
mockFolderService(mock)
folderResponse := &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(folderResponse, nil).Once()
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
@ -249,10 +222,8 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
t.Run("When trying to override inherited permissions with lower precedence", func(t *testing.T) {
origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
@ -261,15 +232,8 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride},
)
mock := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
},
}
mockFolderService(mock)
folderResponse := &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(folderResponse, nil).Once()
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
@ -291,14 +255,12 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
t.Run("Getting and updating folder permissions with hidden users", func(t *testing.T) {
origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
settings.HiddenUsers = map[string]struct{}{
"hiddenUser": {},
testUserLogin: {},
}
t.Cleanup(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
settings.HiddenUsers = make(map[string]struct{})
})
@ -315,15 +277,13 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
},
})
mock := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
},
}
var gotItems []*models.DashboardAcl
mockFolderService(mock)
folderResponse := &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(folderResponse, nil).Twice()
dashboardStore.On("UpdateDashboardACL", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
gotItems = args.Get(2).([]*models.DashboardAcl)
}).Return(nil).Once()
var resp []*models.DashboardAclInfoDTO
mockSQLStore := mockstore.NewSQLStoreMock()
@ -360,16 +320,6 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
routePattern: "/api/folders/:uid/permissions",
cmd: cmd,
fn: func(sc *scenarioContext) {
origUpdateDashboardACL := updateDashboardACL
t.Cleanup(func() {
updateDashboardACL = origUpdateDashboardACL
})
var gotItems []*models.DashboardAcl
updateDashboardACL = func(_ context.Context, _ dashboardifaces.Store, _ int64, items []*models.DashboardAcl) error {
gotItems = items
return nil
}
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
assert.Len(t, gotItems, 4)
@ -385,21 +335,12 @@ func callGetFolderPermissions(sc *scenarioContext, hs *HTTPServer) {
func callUpdateFolderPermissions(t *testing.T, sc *scenarioContext) {
t.Helper()
origUpdateDashboardACL := updateDashboardACL
t.Cleanup(func() {
updateDashboardACL = origUpdateDashboardACL
})
updateDashboardACL = func(_ context.Context, _ dashboardifaces.Store, dashID int64, items []*models.DashboardAcl) error {
return nil
}
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func updateFolderPermissionScenario(t *testing.T, ctx updatePermissionContext, hs *HTTPServer) {
t.Run(fmt.Sprintf("%s %s", ctx.desc, ctx.url), func(t *testing.T) {
defer bus.ClearBusHandlers()
t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(t, ctx.url)

View File

@ -10,26 +10,28 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestFoldersAPIEndpoint(t *testing.T) {
folderService := &dashboards.FakeFolderService{}
defer folderService.AssertExpectations(t)
t.Run("Given a correct request for creating a folder", func(t *testing.T) {
cmd := models.CreateFolderCommand{
Uid: "uid",
Title: "Folder",
}
mock := &fakeFolderService{
CreateFolderResult: &models.Folder{Id: 1, Uid: "uid", Title: "Folder"},
}
folderResult := &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
folderService.On("CreateFolder", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(folderResult, nil).Once()
createFolderScenario(t, "When calling POST on", "/api/folders", "/api/folders", mock, cmd,
createFolderScenario(t, "When calling POST on", "/api/folders", "/api/folders", folderService, cmd,
func(sc *scenarioContext) {
callCreateFolder(sc)
@ -64,12 +66,10 @@ func TestFoldersAPIEndpoint(t *testing.T) {
}
for _, tc := range testCases {
mock := &fakeFolderService{
CreateFolderError: tc.Error,
}
folderService.On("CreateFolder", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, tc.Error).Once()
createFolderScenario(t, fmt.Sprintf("Expect '%s' error when calling POST on", tc.Error.Error()),
"/api/folders", "/api/folders", mock, cmd, func(sc *scenarioContext) {
"/api/folders", "/api/folders", folderService, cmd, func(sc *scenarioContext) {
callCreateFolder(sc)
assert.Equalf(t, tc.ExpectedStatusCode, sc.resp.Code, "Wrong status code for error %s", tc.Error)
})
@ -81,11 +81,12 @@ func TestFoldersAPIEndpoint(t *testing.T) {
Title: "Folder upd",
}
mock := &fakeFolderService{
UpdateFolderResult: &models.Folder{Id: 1, Uid: "uid", Title: "Folder upd"},
}
folderService.On("UpdateFolder", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
cmd := args.Get(4).(*models.UpdateFolderCommand)
cmd.Result = &models.Folder{Id: 1, Uid: "uid", Title: "Folder upd"}
}).Return(nil).Once()
updateFolderScenario(t, "When calling PUT on", "/api/folders/uid", "/api/folders/:uid", mock, cmd,
updateFolderScenario(t, "When calling PUT on", "/api/folders/uid", "/api/folders/:uid", folderService, cmd,
func(sc *scenarioContext) {
callUpdateFolder(sc)
@ -119,12 +120,9 @@ func TestFoldersAPIEndpoint(t *testing.T) {
}
for _, tc := range testCases {
mock := &fakeFolderService{
UpdateFolderError: tc.Error,
}
folderService.On("UpdateFolder", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.Error).Once()
updateFolderScenario(t, fmt.Sprintf("Expect '%s' error when calling PUT on", tc.Error.Error()),
"/api/folders/uid", "/api/folders/:uid", mock, cmd, func(sc *scenarioContext) {
"/api/folders/uid", "/api/folders/:uid", folderService, cmd, func(sc *scenarioContext) {
callUpdateFolder(sc)
assert.Equalf(t, tc.ExpectedStatusCode, sc.resp.Code, "Wrong status code for %s", tc.Error)
})
@ -136,14 +134,15 @@ func callCreateFolder(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func createFolderScenario(t *testing.T, desc string, url string, routePattern string, mock *fakeFolderService,
func createFolderScenario(t *testing.T, desc string, url string, routePattern string, folderService dashboards.FolderService,
cmd models.CreateFolderCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
hs := HTTPServer{
Bus: bus.GetBus(),
Cfg: setting.NewCfg(),
Bus: bus.GetBus(),
Cfg: setting.NewCfg(),
folderService: folderService,
}
sc := setupScenarioContext(t, url)
@ -156,15 +155,8 @@ func createFolderScenario(t *testing.T, desc string, url string, routePattern st
return hs.CreateFolder(c)
})
origNewFolderService := dashboards.NewFolderService
mockFolderService(mock)
sc.m.Post(routePattern, sc.defaultHandler)
defer func() {
dashboards.NewFolderService = origNewFolderService
}()
fn(sc)
})
}
@ -173,13 +165,14 @@ func callUpdateFolder(sc *scenarioContext) {
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
}
func updateFolderScenario(t *testing.T, desc string, url string, routePattern string, mock *fakeFolderService,
func updateFolderScenario(t *testing.T, desc string, url string, routePattern string, folderService dashboards.FolderService,
cmd models.UpdateFolderCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
hs := HTTPServer{
Cfg: setting.NewCfg(),
Cfg: setting.NewCfg(),
folderService: folderService,
}
sc := setupScenarioContext(t, url)
@ -192,12 +185,6 @@ func updateFolderScenario(t *testing.T, desc string, url string, routePattern st
return hs.UpdateFolder(c)
})
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
dashboards.NewFolderService = origNewFolderService
})
mockFolderService(mock)
sc.m.Put(routePattern, sc.defaultHandler)
fn(sc)
@ -222,35 +209,28 @@ type fakeFolderService struct {
DeletedFolderUids []string
}
func (s *fakeFolderService) GetFolders(ctx context.Context, limit int64, page int64) ([]*models.Folder, error) {
func (s *fakeFolderService) GetFolders(ctx context.Context, user *models.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
return s.GetFoldersResult, s.GetFoldersError
}
func (s *fakeFolderService) GetFolderByID(ctx context.Context, id int64) (*models.Folder, error) {
func (s *fakeFolderService) GetFolderByID(ctx context.Context, user *models.SignedInUser, id int64, orgID int64) (*models.Folder, error) {
return s.GetFolderByIDResult, s.GetFolderByIDError
}
func (s *fakeFolderService) GetFolderByUID(ctx context.Context, uid string) (*models.Folder, error) {
func (s *fakeFolderService) GetFolderByUID(ctx context.Context, user *models.SignedInUser, orgID int64, uid string) (*models.Folder, error) {
return s.GetFolderByUIDResult, s.GetFolderByUIDError
}
func (s *fakeFolderService) CreateFolder(ctx context.Context, title, uid string) (*models.Folder, error) {
func (s *fakeFolderService) CreateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, title, uid string) (*models.Folder, error) {
return s.CreateFolderResult, s.CreateFolderError
}
func (s *fakeFolderService) UpdateFolder(ctx context.Context, existingUID string, cmd *models.UpdateFolderCommand) error {
func (s *fakeFolderService) UpdateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
cmd.Result = s.UpdateFolderResult
return s.UpdateFolderError
}
func (s *fakeFolderService) DeleteFolder(ctx context.Context, uid string, forceDeleteRules bool) (*models.Folder, error) {
func (s *fakeFolderService) DeleteFolder(ctx context.Context, user *models.SignedInUser, orgID int64, uid string, forceDeleteRules bool) (*models.Folder, error) {
s.DeletedFolderUids = append(s.DeletedFolderUids, uid)
return s.DeleteFolderResult, s.DeleteFolderError
}
func mockFolderService(mock *fakeFolderService) {
dashboards.NewFolderService = func(orgId int64, user *models.SignedInUser,
dashboardStore dboards.Store) dashboards.FolderService {
return mock
}
}

View File

@ -33,6 +33,7 @@ import (
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/cleanup"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/encryption"
@ -130,6 +131,9 @@ type HTTPServer struct {
authInfoService login.AuthInfoService
TeamPermissionsService *resourcepermissions.Service
NotificationService *notifications.NotificationService
dashboardService dashboards.DashboardService
dashboardProvisioningService dashboards.DashboardProvisioningService
folderService dashboards.FolderService
DatasourcePermissionsService DatasourcePermissionsService
}
@ -158,7 +162,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
dataSourcesService datasources.DataSourceService, secretsService secrets.Service, queryDataService *query.Service,
ldapGroups ldap.Groups, teamGuardian teamguardian.TeamGuardian, serviceaccountsService serviceaccounts.Service,
authInfoService login.AuthInfoService, resourcePermissionServices *resourceservices.ResourceServices,
notificationService *notifications.NotificationService, datasourcePermissionsService DatasourcePermissionsService) (*HTTPServer, error) {
notificationService *notifications.NotificationService, dashboardService dashboards.DashboardService, dashboardProvisioningService dashboards.DashboardProvisioningService,
folderService dashboards.FolderService, datasourcePermissionsService DatasourcePermissionsService) (*HTTPServer, error) {
web.Env = cfg.Env
m := web.New()
@ -219,6 +224,9 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
authInfoService: authInfoService,
TeamPermissionsService: resourcePermissionServices.GetTeamService(),
NotificationService: notificationService,
dashboardService: dashboardService,
dashboardProvisioningService: dashboardProvisioningService,
folderService: folderService,
DatasourcePermissionsService: datasourcePermissionsService,
}
if hs.Listener != nil {

View File

@ -150,7 +150,7 @@ func TestOrgUsersAPIEndpoint_LegacyAccessControl_FolderAdmin(t *testing.T) {
"tags": "prod",
}),
}
folder, err := sc.db.SaveDashboard(cmd)
folder, err := sc.dashboardsStore.SaveDashboard(cmd)
require.NoError(t, err)
require.NotNil(t, folder)
@ -165,7 +165,7 @@ func TestOrgUsersAPIEndpoint_LegacyAccessControl_FolderAdmin(t *testing.T) {
Updated: time.Now(),
},
}
err = sc.db.UpdateDashboardACL(context.Background(), folder.Id, acls)
err = sc.dashboardsStore.UpdateDashboardACL(context.Background(), folder.Id, acls)
require.NoError(t, err)
response := callAPI(sc.server, http.MethodGet, "/api/org/users/lookup", nil, t)

View File

@ -1,23 +0,0 @@
package dashboards
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
// Store is a dashboard store.
type Store interface {
// ValidateDashboardBeforeSave validates a dashboard before save.
ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error)
// GetFolderByTitle retrieves a dashboard by its title and is used by unified alerting
GetFolderByTitle(orgID int64, title string) (*models.Dashboard, error)
GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)
UpdateDashboardACLCtx(ctx context.Context, uid int64, items []*models.DashboardAcl) error
// SaveAlerts saves dashboard alerts.
SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error
}

View File

@ -456,7 +456,3 @@ type GetDashboardRefByIdQuery struct {
Id int64
Result *DashboardRef
}
type UnprovisionDashboardCommand struct {
Id int64
}

View File

@ -11,6 +11,9 @@ import (
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
"github.com/grafana/grafana/pkg/plugins/manager/loader"
"github.com/grafana/grafana/pkg/plugins/manager/signature"
"github.com/grafana/grafana/pkg/services/dashboards/database"
service "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
@ -24,8 +27,9 @@ func TestGetPluginDashboards(t *testing.T) {
},
}
pmCfg := plugins.FromGrafanaCfg(cfg)
dashboardService := service.ProvideDashboardService(database.ProvideDashboardStore(&sqlstore.SQLStore{}))
pm, err := ProvideService(cfg, loader.New(pmCfg, nil,
signature.NewUnsignedAuthorizer(pmCfg), &provider.Service{}))
signature.NewUnsignedAuthorizer(pmCfg), &provider.Service{}), dashboardService)
require.NoError(t, err)
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error {

View File

@ -9,12 +9,12 @@ import (
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
"github.com/grafana/grafana/pkg/plugins/manager/installer"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
)
@ -30,35 +30,38 @@ var _ plugins.StaticRouteResolver = (*PluginManager)(nil)
var _ plugins.RendererManager = (*PluginManager)(nil)
type PluginManager struct {
cfg *plugins.Cfg
store map[string]*plugins.Plugin
pluginInstaller plugins.Installer
pluginLoader plugins.Loader
pluginsMu sync.RWMutex
pluginPaths map[plugins.Class][]string
log log.Logger
cfg *plugins.Cfg
store map[string]*plugins.Plugin
pluginInstaller plugins.Installer
pluginLoader plugins.Loader
pluginsMu sync.RWMutex
pluginPaths map[plugins.Class][]string
dashboardService dashboards.DashboardService
log log.Logger
}
func ProvideService(grafanaCfg *setting.Cfg, pluginLoader plugins.Loader) (*PluginManager, error) {
func ProvideService(grafanaCfg *setting.Cfg, pluginLoader plugins.Loader, dashboardService dashboards.DashboardService) (*PluginManager, error) {
pm := New(plugins.FromGrafanaCfg(grafanaCfg), map[plugins.Class][]string{
plugins.Core: corePluginPaths(grafanaCfg),
plugins.Bundled: {grafanaCfg.BundledPluginsPath},
plugins.External: append([]string{grafanaCfg.PluginsPath}, pluginSettingPaths(grafanaCfg)...),
}, pluginLoader)
}, pluginLoader, dashboardService)
if err := pm.Init(); err != nil {
return nil, err
}
return pm, nil
}
func New(cfg *plugins.Cfg, pluginPaths map[plugins.Class][]string, pluginLoader plugins.Loader) *PluginManager {
func New(cfg *plugins.Cfg, pluginPaths map[plugins.Class][]string, pluginLoader plugins.Loader,
dashboardService dashboards.DashboardService) *PluginManager {
return &PluginManager{
cfg: cfg,
pluginLoader: pluginLoader,
pluginPaths: pluginPaths,
store: make(map[string]*plugins.Plugin),
log: log.New("plugin.manager"),
pluginInstaller: installer.New(false, cfg.BuildVersion, newInstallerLogger("plugin.installer", true)),
cfg: cfg,
pluginLoader: pluginLoader,
pluginPaths: pluginPaths,
store: make(map[string]*plugins.Plugin),
log: log.New("plugin.manager"),
pluginInstaller: installer.New(false, cfg.BuildVersion, newInstallerLogger("plugin.installer", true)),
dashboardService: dashboardService,
}
}

View File

@ -7,16 +7,14 @@ import (
"strings"
"testing"
"github.com/grafana/grafana/pkg/infra/tracing"
"go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
"github.com/grafana/grafana/pkg/plugins/manager/loader"
"github.com/grafana/grafana/pkg/plugins/manager/signature"
service "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/services/searchV2"
@ -37,6 +35,7 @@ import (
"github.com/grafana/grafana/pkg/tsdb/prometheus"
"github.com/grafana/grafana/pkg/tsdb/tempo"
"github.com/grafana/grafana/pkg/tsdb/testdatasource"
"go.opentelemetry.io/otel/trace"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -95,7 +94,7 @@ func TestPluginManager_int_init(t *testing.T) {
pmCfg := plugins.FromGrafanaCfg(cfg)
pm, err := ProvideService(cfg, loader.New(pmCfg, license, signature.NewUnsignedAuthorizer(pmCfg),
provider.ProvideService(coreRegistry)))
provider.ProvideService(coreRegistry)), &service.DashboardServiceImpl{})
require.NoError(t, err)
verifyCorePluginCatalogue(t, pm)

View File

@ -12,6 +12,9 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/services/dashboards/database"
service "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -466,7 +469,8 @@ func TestPluginManager_lifecycle_unmanaged(t *testing.T) {
func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager {
t.Helper()
pm := New(&plugins.Cfg{}, nil, &fakeLoader{})
dashboardService := service.ProvideDashboardService(database.ProvideDashboardStore(&sqlstore.SQLStore{}))
pm := New(&plugins.Cfg{}, nil, &fakeLoader{}, dashboardService)
for _, cb := range cbs {
cb(pm)
@ -520,7 +524,8 @@ func newScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *managerS
cfg.Azure.ManagedIdentityClientId = "client-id"
loader := &fakeLoader{}
manager := New(cfg, nil, loader)
dashboardService := service.ProvideDashboardService(database.ProvideDashboardStore(&sqlstore.SQLStore{}))
manager := New(cfg, nil, loader, dashboardService)
manager.pluginLoader = loader
ctx := &managerScenarioCtx{
manager: manager,

View File

@ -34,6 +34,9 @@ import (
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/dashboardimport"
dashboardimportservice "github.com/grafana/grafana/pkg/services/dashboardimport/service"
"github.com/grafana/grafana/pkg/services/dashboards"
dashboardstore "github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
"github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources"
@ -200,6 +203,13 @@ var wireBasicSet = wire.NewSet(
featuremgmt.ProvideManagerService,
featuremgmt.ProvideToggles,
resourceservices.ProvideResourceServices,
dashboardservice.ProvideDashboardService,
dashboardservice.ProvideFolderService,
dashboardstore.ProvideDashboardStore,
wire.Bind(new(dashboards.DashboardService), new(*dashboardservice.DashboardServiceImpl)),
wire.Bind(new(dashboards.DashboardProvisioningService), new(*dashboardservice.DashboardServiceImpl)),
wire.Bind(new(dashboards.FolderService), new(*dashboardservice.FolderServiceImpl)),
wire.Bind(new(dashboards.Store), new(*dashboardstore.DashboardStore)),
dashboardimportservice.ProvideService,
wire.Bind(new(dashboardimport.Service), new(*dashboardimportservice.ImportDashboardService)),
plugindashboards.ProvideService,

View File

@ -13,16 +13,15 @@ import (
"github.com/grafana/grafana/pkg/services/librarypanels"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/schemaloader"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
func ProvideService(sqlStore *sqlstore.SQLStore, routeRegister routing.RouteRegister,
func ProvideService(routeRegister routing.RouteRegister,
quotaService *quota.QuotaService, schemaLoaderService *schemaloader.SchemaLoaderService,
pluginDashboardManager plugins.PluginDashboardManager, pluginStore plugins.Store,
libraryPanelService librarypanels.Service) *ImportDashboardService {
libraryPanelService librarypanels.Service, dashboardService dashboards.DashboardService) *ImportDashboardService {
s := &ImportDashboardService{
pluginDashboardManager: pluginDashboardManager,
dashboardService: dashboards.NewService(sqlStore),
dashboardService: dashboardService,
libraryPanelService: libraryPanelService,
}

View File

@ -1,51 +0,0 @@
package dashboards
import (
"context"
"time"
"github.com/grafana/grafana/pkg/models"
)
func (dr *dashboardServiceImpl) MakeUserAdmin(ctx context.Context, orgID int64, userID int64, dashboardID int64, setViewAndEditPermissions bool) error {
rtEditor := models.ROLE_EDITOR
rtViewer := models.ROLE_VIEWER
items := []*models.DashboardAcl{
{
OrgID: orgID,
DashboardID: dashboardID,
UserID: userID,
Permission: models.PERMISSION_ADMIN,
Created: time.Now(),
Updated: time.Now(),
},
}
if setViewAndEditPermissions {
items = append(items,
&models.DashboardAcl{
OrgID: orgID,
DashboardID: dashboardID,
Role: &rtEditor,
Permission: models.PERMISSION_EDIT,
Created: time.Now(),
Updated: time.Now(),
},
&models.DashboardAcl{
OrgID: orgID,
DashboardID: dashboardID,
Role: &rtViewer,
Permission: models.PERMISSION_VIEW,
Created: time.Now(),
Updated: time.Now(),
},
)
}
if err := dr.dashboardStore.UpdateDashboardACLCtx(ctx, dashboardID, items); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,47 @@
package dashboards
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
// DashboardService is a service for operating on dashboards.
type DashboardService interface {
SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error)
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error
BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error)
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
}
//go:generate mockery --name DashboardProvisioningService --structname FakeDashboardProvisioning --inpackage --filename dashboard_provisioning_mock.go
// DashboardProvisioningService is a service for operating on provisioned dashboards.
type DashboardProvisioningService interface {
SaveProvisionedDashboard(ctx context.Context, dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveFolderForProvisionedDashboards(context.Context, *SaveDashboardDTO) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
UnprovisionDashboard(ctx context.Context, dashboardID int64) error
DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error
}
//go:generate mockery --name Store --structname FakeDashboardStore --output database --outpkg database --filename database_mock.go
// Store is a dashboard store.
type Store interface {
// ValidateDashboardBeforeSave validates a dashboard before save.
ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error)
// GetFolderByTitle retrieves a dashboard by its title and is used by unified alerting
GetFolderByTitle(orgID int64, title string) (*models.Dashboard, error)
GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
// SaveAlerts saves dashboard alerts.
SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error
UnprovisionDashboard(ctx context.Context, id int64) error
}

View File

@ -0,0 +1,158 @@
// Code generated by mockery v2.10.0. DO NOT EDIT.
package dashboards
import (
context "context"
models "github.com/grafana/grafana/pkg/models"
mock "github.com/stretchr/testify/mock"
)
// FakeDashboardProvisioning is an autogenerated mock type for the DashboardProvisioningService type
type FakeDashboardProvisioning struct {
mock.Mock
}
// DeleteProvisionedDashboard provides a mock function with given fields: ctx, dashboardID, orgID
func (_m *FakeDashboardProvisioning) DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error {
ret := _m.Called(ctx, dashboardID, orgID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
r0 = rf(ctx, dashboardID, orgID)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetProvisionedDashboardData provides a mock function with given fields: name
func (_m *FakeDashboardProvisioning) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
ret := _m.Called(name)
var r0 []*models.DashboardProvisioning
if rf, ok := ret.Get(0).(func(string) []*models.DashboardProvisioning); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.DashboardProvisioning)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetProvisionedDashboardDataByDashboardID provides a mock function with given fields: dashboardID
func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
ret := _m.Called(dashboardID)
var r0 *models.DashboardProvisioning
if rf, ok := ret.Get(0).(func(int64) *models.DashboardProvisioning); ok {
r0 = rf(dashboardID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.DashboardProvisioning)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(int64) error); ok {
r1 = rf(dashboardID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetProvisionedDashboardDataByDashboardUID provides a mock function with given fields: orgID, dashboardUID
func (_m *FakeDashboardProvisioning) GetProvisionedDashboardDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error) {
ret := _m.Called(orgID, dashboardUID)
var r0 *models.DashboardProvisioning
if rf, ok := ret.Get(0).(func(int64, string) *models.DashboardProvisioning); ok {
r0 = rf(orgID, dashboardUID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.DashboardProvisioning)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(int64, string) error); ok {
r1 = rf(orgID, dashboardUID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SaveFolderForProvisionedDashboards provides a mock function with given fields: _a0, _a1
func (_m *FakeDashboardProvisioning) SaveFolderForProvisionedDashboards(_a0 context.Context, _a1 *SaveDashboardDTO) (*models.Dashboard, error) {
ret := _m.Called(_a0, _a1)
var r0 *models.Dashboard
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO) *models.Dashboard); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Dashboard)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *SaveDashboardDTO) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SaveProvisionedDashboard provides a mock function with given fields: ctx, dto, provisioning
func (_m *FakeDashboardProvisioning) SaveProvisionedDashboard(ctx context.Context, dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
ret := _m.Called(ctx, dto, provisioning)
var r0 *models.Dashboard
if rf, ok := ret.Get(0).(func(context.Context, *SaveDashboardDTO, *models.DashboardProvisioning) *models.Dashboard); ok {
r0 = rf(ctx, dto, provisioning)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Dashboard)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *SaveDashboardDTO, *models.DashboardProvisioning) error); ok {
r1 = rf(ctx, dto, provisioning)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UnprovisionDashboard provides a mock function with given fields: ctx, dashboardID
func (_m *FakeDashboardProvisioning) UnprovisionDashboard(ctx context.Context, dashboardID int64) error {
ret := _m.Called(ctx, dashboardID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, dashboardID)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -0,0 +1,44 @@
package dashboards
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
type FakeDashboardService struct {
DashboardService
SaveDashboardResult *models.Dashboard
SaveDashboardError error
SavedDashboards []*SaveDashboardDTO
ProvisionedDashData *models.DashboardProvisioning
}
func (s *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) {
s.SavedDashboards = append(s.SavedDashboards, dto)
if s.SaveDashboardResult == nil && s.SaveDashboardError == nil {
s.SaveDashboardResult = dto.Dashboard
}
return s.SaveDashboardResult, s.SaveDashboardError
}
func (s *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) {
return s.SaveDashboard(ctx, dto, true)
}
func (s *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
for index, dash := range s.SavedDashboards {
if dash.Dashboard.Id == dashboardId && dash.OrgId == orgId {
s.SavedDashboards = append(s.SavedDashboards[:index], s.SavedDashboards[index+1:]...)
break
}
}
return nil
}
func (s *FakeDashboardService) GetProvisionedDashboardDataByDashboardID(id int64) (*models.DashboardProvisioning, error) {
return s.ProvisionedDashData, nil
}

View File

@ -0,0 +1,611 @@
package database
import (
"context"
"fmt"
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/util"
)
type DashboardStore struct {
sqlStore *sqlstore.SQLStore
log log.Logger
}
func ProvideDashboardStore(sqlStore *sqlstore.SQLStore) *DashboardStore {
return &DashboardStore{sqlStore: sqlStore, log: log.New("dashboard-store")}
}
func (d *DashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
isParentFolderChanged := false
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
var err error
isParentFolderChanged, err = getExistingDashboardByIdOrUidForUpdate(sess, dashboard, d.sqlStore.Dialect, overwrite)
if err != nil {
return err
}
isParentFolderChanged, err = getExistingDashboardByTitleAndFolder(sess, dashboard, d.sqlStore.Dialect, overwrite,
isParentFolderChanged)
if err != nil {
return err
}
return nil
})
if err != nil {
return false, err
}
return isParentFolderChanged, nil
}
func (d *DashboardStore) GetFolderByTitle(orgID int64, title string) (*models.Dashboard, error) {
if title == "" {
return nil, models.ErrDashboardIdentifierNotSet
}
// there is a unique constraint on org_id, folder_id, title
// there are no nested folders so the parent folder id is always 0
dashboard := models.Dashboard{OrgId: orgID, FolderId: 0, Title: title}
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
has, err := sess.Table(&models.Dashboard{}).Where("is_folder = " + d.sqlStore.Dialect.BooleanStr(true)).Where("folder_id=0").Get(&dashboard)
if err != nil {
return err
}
if !has {
return models.ErrDashboardNotFound
}
dashboard.SetId(dashboard.Id)
dashboard.SetUid(dashboard.Uid)
return nil
})
return &dashboard, err
}
func (d *DashboardStore) GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
var data models.DashboardProvisioning
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
_, err := sess.Where("dashboard_id = ?", dashboardID).Get(&data)
return err
})
if data.DashboardId == 0 {
return nil, nil
}
return &data, err
}
func (d *DashboardStore) GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error) {
var provisionedDashboard models.DashboardProvisioning
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
var dashboard models.Dashboard
exists, err := sess.Where("org_id = ? AND uid = ?", orgID, dashboardUID).Get(&dashboard)
if err != nil {
return err
}
if !exists {
return models.
ErrDashboardNotFound
}
exists, err = sess.Where("dashboard_id = ?", dashboard.Id).Get(&provisionedDashboard)
if err != nil {
return err
}
if !exists {
return models.ErrProvisionedDashboardNotFound
}
return nil
})
return &provisionedDashboard, err
}
func (d *DashboardStore) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
var result []*models.DashboardProvisioning
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
return sess.Where("name = ?", name).Find(&result)
})
return result, err
}
func (d *DashboardStore) SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
if err := saveDashboard(sess, &cmd); err != nil {
return err
}
if provisioning.Updated == 0 {
provisioning.Updated = cmd.Result.Updated.Unix()
}
return saveProvisionedData(sess, provisioning, cmd.Result)
})
return cmd.Result, err
}
func (d *DashboardStore) SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) {
err := d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
return saveDashboard(sess, &cmd)
})
return cmd.Result, err
}
func (d *DashboardStore) UpdateDashboardACL(ctx context.Context, dashboardID int64, items []*models.DashboardAcl) error {
return d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
// delete existing items
_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", dashboardID)
if err != nil {
return fmt.Errorf("deleting from dashboard_acl failed: %w", err)
}
for _, item := range items {
if item.UserID == 0 && item.TeamID == 0 && (item.Role == nil || !item.Role.IsValid()) {
return models.ErrDashboardAclInfoMissing
}
if item.DashboardID == 0 {
return models.ErrDashboardPermissionDashboardEmpty
}
sess.Nullable("user_id", "team_id")
if _, err := sess.Insert(item); err != nil {
return err
}
}
// Update dashboard HasAcl flag
dashboard := models.Dashboard{HasAcl: true}
_, err = sess.Cols("has_acl").Where("id=?", dashboardID).Update(&dashboard)
return err
})
}
func (d *DashboardStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error {
return d.sqlStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
existingAlerts, err := GetAlertsByDashboardId2(dashID, sess)
if err != nil {
return err
}
if err := updateAlerts(existingAlerts, alerts, sess, d.log); err != nil {
return err
}
if err := deleteMissingAlerts(existingAlerts, alerts, sess, d.log); err != nil {
return err
}
return nil
})
}
// UnprovisionDashboard removes row in dashboard_provisioning for the dashboard making it seem as if manually created.
// The dashboard will still have `created_by = -1` to see it was not created by any particular user.
func (d *DashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error {
return d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
_, err := sess.Where("dashboard_id = ?", id).Delete(&models.DashboardProvisioning{})
return err
})
}
func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *models.Dashboard, dialect migrator.Dialect, overwrite bool) (bool, error) {
dashWithIdExists := false
isParentFolderChanged := false
var existingById models.Dashboard
if dash.Id > 0 {
var err error
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
if err != nil {
return false, fmt.Errorf("SQL query for existing dashboard by ID failed: %w", err)
}
if !dashWithIdExists {
return false, models.ErrDashboardNotFound
}
if dash.Uid == "" {
dash.SetUid(existingById.Uid)
}
}
dashWithUidExists := false
var existingByUid models.Dashboard
if dash.Uid != "" {
var err error
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
if err != nil {
return false, fmt.Errorf("SQL query for existing dashboard by UID failed: %w", err)
}
}
if dash.FolderId > 0 {
var existingFolder models.Dashboard
folderExists, err := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId,
dialect.BooleanStr(true)).Get(&existingFolder)
if err != nil {
return false, fmt.Errorf("SQL query for folder failed: %w", err)
}
if !folderExists {
return false, models.ErrDashboardFolderNotFound
}
}
if !dashWithIdExists && !dashWithUidExists {
return false, nil
}
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
return false, models.ErrDashboardWithSameUIDExists
}
existing := existingById
if !dashWithIdExists && dashWithUidExists {
dash.SetId(existingByUid.Id)
dash.SetUid(existingByUid.Uid)
existing = existingByUid
if !dash.IsFolder {
isParentFolderChanged = true
}
}
if (existing.IsFolder && !dash.IsFolder) ||
(!existing.IsFolder && dash.IsFolder) {
return isParentFolderChanged, models.ErrDashboardTypeMismatch
}
if !dash.IsFolder && dash.FolderId != existing.FolderId {
isParentFolderChanged = true
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if overwrite {
dash.SetVersion(existing.Version)
} else {
return isParentFolderChanged, models.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && !overwrite {
return isParentFolderChanged, models.UpdatePluginDashboardError{PluginId: existing.PluginId}
}
return isParentFolderChanged, nil
}
func getExistingDashboardByTitleAndFolder(sess *sqlstore.DBSession, dash *models.Dashboard, dialect migrator.Dialect, overwrite,
isParentFolderChanged bool) (bool, error) {
var existing models.Dashboard
exists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug,
dialect.BooleanStr(true), dash.FolderId).Get(&existing)
if err != nil {
return isParentFolderChanged, fmt.Errorf("SQL query for existing dashboard by org ID or folder ID failed: %w", err)
}
if exists && dash.Id != existing.Id {
if existing.IsFolder && !dash.IsFolder {
return isParentFolderChanged, models.ErrDashboardWithSameNameAsFolder
}
if !existing.IsFolder && dash.IsFolder {
return isParentFolderChanged, models.ErrDashboardFolderWithSameNameAsDashboard
}
if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) {
isParentFolderChanged = true
}
if overwrite {
dash.SetId(existing.Id)
dash.SetUid(existing.Uid)
dash.SetVersion(existing.Version)
} else {
return isParentFolderChanged, models.ErrDashboardWithSameNameInFolderExists
}
}
return isParentFolderChanged, nil
}
func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) error {
dash := cmd.GetDashboardModel()
userId := cmd.UserId
if userId == 0 {
userId = -1
}
if dash.Id > 0 {
var existing models.Dashboard
dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
if err != nil {
return err
}
if !dashWithIdExists {
return models.ErrDashboardNotFound
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if cmd.Overwrite {
dash.SetVersion(existing.Version)
} else {
return models.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && !cmd.Overwrite {
return models.UpdatePluginDashboardError{PluginId: existing.PluginId}
}
}
if dash.Uid == "" {
uid, err := generateNewDashboardUid(sess, dash.OrgId)
if err != nil {
return err
}
dash.SetUid(uid)
}
parentVersion := dash.Version
var affectedRows int64
var err error
if dash.Id == 0 {
dash.SetVersion(1)
dash.Created = time.Now()
dash.CreatedBy = userId
dash.Updated = time.Now()
dash.UpdatedBy = userId
metrics.MApiDashboardInsert.Inc()
affectedRows, err = sess.Insert(dash)
} else {
dash.SetVersion(dash.Version + 1)
if !cmd.UpdatedAt.IsZero() {
dash.Updated = cmd.UpdatedAt
} else {
dash.Updated = time.Now()
}
dash.UpdatedBy = userId
affectedRows, err = sess.MustCols("folder_id").ID(dash.Id).Update(dash)
}
if err != nil {
return err
}
if affectedRows == 0 {
return models.ErrDashboardNotFound
}
dashVersion := &models.DashboardVersion{
DashboardId: dash.Id,
ParentVersion: parentVersion,
RestoredFrom: cmd.RestoredFrom,
Version: dash.Version,
Created: time.Now(),
CreatedBy: dash.UpdatedBy,
Message: cmd.Message,
Data: dash.Data,
}
// insert version entry
if affectedRows, err = sess.Insert(dashVersion); err != nil {
return err
} else if affectedRows == 0 {
return models.ErrDashboardNotFound
}
// delete existing tags
_, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
if err != nil {
return err
}
// insert new tags
tags := dash.GetTags()
if len(tags) > 0 {
for _, tag := range tags {
if _, err := sess.Insert(&sqlstore.DashboardTag{DashboardId: dash.Id, Term: tag}); err != nil {
return err
}
}
}
cmd.Result = dash
return nil
}
func generateNewDashboardUid(sess *sqlstore.DBSession, orgId int64) (string, error) {
for i := 0; i < 3; i++ {
uid := util.GenerateShortUID()
exists, err := sess.Where("org_id=? AND uid=?", orgId, uid).Get(&models.Dashboard{})
if err != nil {
return "", err
}
if !exists {
return uid, nil
}
}
return "", models.ErrDashboardFailedGenerateUniqueUid
}
func saveProvisionedData(sess *sqlstore.DBSession, provisioning *models.DashboardProvisioning, dashboard *models.Dashboard) error {
result := &models.DashboardProvisioning{}
exist, err := sess.Where("dashboard_id=? AND name = ?", dashboard.Id, provisioning.Name).Get(result)
if err != nil {
return err
}
provisioning.Id = result.Id
provisioning.DashboardId = dashboard.Id
if exist {
_, err = sess.ID(result.Id).Update(provisioning)
} else {
_, err = sess.Insert(provisioning)
}
return err
}
func GetAlertsByDashboardId2(dashboardId int64, sess *sqlstore.DBSession) ([]*models.Alert, error) {
alerts := make([]*models.Alert, 0)
err := sess.Where("dashboard_id = ?", dashboardId).Find(&alerts)
if err != nil {
return []*models.Alert{}, err
}
return alerts, nil
}
func updateAlerts(existingAlerts []*models.Alert, alerts []*models.Alert, sess *sqlstore.DBSession, log log.Logger) error {
for _, alert := range alerts {
update := false
var alertToUpdate *models.Alert
for _, k := range existingAlerts {
if alert.PanelId == k.PanelId {
update = true
alert.Id = k.Id
alertToUpdate = k
break
}
}
if update {
if alertToUpdate.ContainsUpdates(alert) {
alert.Updated = time.Now()
alert.State = alertToUpdate.State
sess.MustCols("message", "for")
_, err := sess.ID(alert.Id).Update(alert)
if err != nil {
return err
}
log.Debug("Alert updated", "name", alert.Name, "id", alert.Id)
}
} else {
alert.Updated = time.Now()
alert.Created = time.Now()
alert.State = models.AlertStateUnknown
alert.NewStateDate = time.Now()
_, err := sess.Insert(alert)
if err != nil {
return err
}
log.Debug("Alert inserted", "name", alert.Name, "id", alert.Id)
}
tags := alert.GetTagsFromSettings()
if _, err := sess.Exec("DELETE FROM alert_rule_tag WHERE alert_id = ?", alert.Id); err != nil {
return err
}
if tags != nil {
tags, err := EnsureTagsExist(sess, tags)
if err != nil {
return err
}
for _, tag := range tags {
if _, err := sess.Exec("INSERT INTO alert_rule_tag (alert_id, tag_id) VALUES(?,?)", alert.Id, tag.Id); err != nil {
return err
}
}
}
}
return nil
}
func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, sess *sqlstore.DBSession, log log.Logger) error {
for _, missingAlert := range alerts {
missing := true
for _, k := range existingAlerts {
if missingAlert.PanelId == k.PanelId {
missing = false
break
}
}
if missing {
if err := deleteAlertByIdInternal(missingAlert.Id, "Removed from dashboard", sess, log); err != nil {
// No use trying to delete more, since we're in a transaction and it will be
// rolled back on error.
return err
}
}
}
return nil
}
func deleteAlertByIdInternal(alertId int64, reason string, sess *sqlstore.DBSession, log log.Logger) error {
log.Debug("Deleting alert", "id", alertId, "reason", reason)
if _, err := sess.Exec("DELETE FROM alert WHERE id = ?", alertId); err != nil {
return err
}
if _, err := sess.Exec("DELETE FROM annotation WHERE alert_id = ?", alertId); err != nil {
return err
}
if _, err := sess.Exec("DELETE FROM alert_notification_state WHERE alert_id = ?", alertId); err != nil {
return err
}
if _, err := sess.Exec("DELETE FROM alert_rule_tag WHERE alert_id = ?", alertId); err != nil {
return err
}
return nil
}
func EnsureTagsExist(sess *sqlstore.DBSession, tags []*models.Tag) ([]*models.Tag, error) {
for _, tag := range tags {
var existingTag models.Tag
// check if it exists
exists, err := sess.Table("tag").Where("`key`=? AND `value`=?", tag.Key, tag.Value).Get(&existingTag)
if err != nil {
return nil, err
}
if exists {
tag.Id = existingTag.Id
} else {
_, err := sess.Table("tag").Insert(tag)
if err != nil {
return nil, err
}
}
}
return tags, nil
}

View File

@ -1,7 +1,7 @@
//go:build integration
// +build integration
package sqlstore
package database
import (
"context"
@ -14,24 +14,25 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDashboardDataAccess(t *testing.T) {
var sqlStore *SQLStore
var sqlStore *sqlstore.SQLStore
var savedFolder, savedDash, savedDash2 *models.Dashboard
var dashboardStore *DashboardStore
setup := func() {
sqlStore = InitTestDB(t)
savedFolder = insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
savedDash = insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
insertTestDashboard(t, sqlStore, "test dash 45", 1, savedFolder.Id, false, "prod")
savedDash2 = insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod")
sqlStore = sqlstore.InitTestDB(t)
dashboardStore = ProvideDashboardStore(sqlStore)
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
savedDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.Id, false, "prod")
savedDash2 = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod")
insertTestRule(t, sqlStore, savedFolder.OrgId, savedFolder.Uid)
}
@ -115,7 +116,7 @@ func TestDashboardDataAccess(t *testing.T) {
t.Run("Should be able to delete dashboard", func(t *testing.T) {
setup()
dash := insertTestDashboard(t, sqlStore, "delete me", 1, 0, false, "delete this")
dash := insertTestDashboard(t, dashboardStore, "delete me", 1, 0, false, "delete this")
err := sqlStore.DeleteDashboard(context.Background(), &models.DeleteDashboardCommand{
Id: dash.Id,
@ -124,29 +125,6 @@ func TestDashboardDataAccess(t *testing.T) {
require.NoError(t, err)
})
t.Run("Should retry generation of uid once if it fails.", func(t *testing.T) {
setup()
timesCalled := 0
generateNewUid = func() string {
timesCalled += 1
if timesCalled <= 2 {
return savedDash.Uid
}
return util.GenerateShortUID()
}
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "new dash 12334",
"tags": []interface{}{},
}),
}
_, err := sqlStore.SaveDashboard(cmd)
require.NoError(t, err)
generateNewUid = util.GenerateShortUID
})
t.Run("Should be able to create dashboard", func(t *testing.T) {
setup()
cmd := models.SaveDashboardCommand{
@ -157,13 +135,14 @@ func TestDashboardDataAccess(t *testing.T) {
}),
UserId: 100,
}
dashboard, err := sqlStore.SaveDashboard(cmd)
dashboard, err := dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
require.EqualValues(t, dashboard.CreatedBy, 100)
require.False(t, dashboard.Created.IsZero())
require.EqualValues(t, dashboard.UpdatedBy, 100)
require.False(t, dashboard.Updated.IsZero())
})
t.Run("Should be able to update dashboard by id and remove folderId", func(t *testing.T) {
setup()
cmd := models.SaveDashboardCommand{
@ -177,7 +156,7 @@ func TestDashboardDataAccess(t *testing.T) {
FolderId: 2,
UserId: 100,
}
dash, err := sqlStore.SaveDashboard(cmd)
dash, err := dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
require.EqualValues(t, dash.FolderId, 2)
@ -192,7 +171,7 @@ func TestDashboardDataAccess(t *testing.T) {
Overwrite: true,
UserId: 100,
}
_, err = sqlStore.SaveDashboard(cmd)
_, err = dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
query := models.GetDashboardQuery{
@ -211,7 +190,7 @@ func TestDashboardDataAccess(t *testing.T) {
t.Run("Should be able to delete empty folder", func(t *testing.T) {
setup()
emptyFolder := insertTestDashboard(t, sqlStore, "2 test dash folder", 1, 0, true, "prod", "webapp")
emptyFolder := insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod", "webapp")
deleteCmd := &models.DeleteDashboardCommand{Id: emptyFolder.Id}
err := sqlStore.DeleteDashboard(context.Background(), deleteCmd)
@ -242,7 +221,7 @@ func TestDashboardDataAccess(t *testing.T) {
require.Equal(t, len(query.Result), 0)
sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
var existingRuleID int64
exists, err := sess.Table("alert_rule").Where("namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)", savedFolder.Id).Cols("id").Get(&existingRuleID)
require.NoError(t, err)
@ -258,7 +237,6 @@ func TestDashboardDataAccess(t *testing.T) {
})
t.Run("Should return error if no dashboard is found for update when dashboard id is greater than zero", func(t *testing.T) {
setup()
cmd := models.SaveDashboardCommand{
OrgId: 1,
Overwrite: true,
@ -269,12 +247,11 @@ func TestDashboardDataAccess(t *testing.T) {
}),
}
_, err := sqlStore.SaveDashboard(cmd)
_, err := dashboardStore.SaveDashboard(cmd)
require.Equal(t, err, models.ErrDashboardNotFound)
})
t.Run("Should not return error if no dashboard is found for update when dashboard id is zero", func(t *testing.T) {
setup()
cmd := models.SaveDashboardCommand{
OrgId: 1,
Overwrite: true,
@ -284,7 +261,7 @@ func TestDashboardDataAccess(t *testing.T) {
"tags": []interface{}{},
}),
}
_, err := sqlStore.SaveDashboard(cmd)
_, err := dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
})
@ -405,7 +382,7 @@ func TestDashboardDataAccess(t *testing.T) {
t.Run("Should be able to search for starred dashboards", func(t *testing.T) {
setup()
starredDash := insertTestDashboard(t, sqlStore, "starred dash", 1, 0, false)
starredDash := insertTestDashboard(t, dashboardStore, "starred dash", 1, 0, false)
err := sqlStore.StarDashboard(context.Background(), &models.StarDashboardCommand{
DashboardId: starredDash.Id,
UserId: 10,
@ -431,29 +408,32 @@ func TestDashboardDataAccess(t *testing.T) {
}
func TestDashboardDataAccessGivenPluginWithImportedDashboards(t *testing.T) {
sqlStore := InitTestDB(t)
sqlStore := sqlstore.InitTestDB(t)
dashboardStore := ProvideDashboardStore(sqlStore)
pluginId := "test-app"
appFolder := insertTestDashboardForPlugin(t, sqlStore, "app-test", 1, 0, true, pluginId)
insertTestDashboardForPlugin(t, sqlStore, "app-dash1", 1, appFolder.Id, false, pluginId)
insertTestDashboardForPlugin(t, sqlStore, "app-dash2", 1, appFolder.Id, false, pluginId)
appFolder := insertTestDashboardForPlugin(t, dashboardStore, "app-test", 1, 0, true, pluginId)
insertTestDashboardForPlugin(t, dashboardStore, "app-dash1", 1, appFolder.Id, false, pluginId)
insertTestDashboardForPlugin(t, dashboardStore, "app-dash2", 1, appFolder.Id, false, pluginId)
query := models.GetDashboardsByPluginIdQuery{
PluginId: pluginId,
OrgId: 1,
}
err := GetDashboardsByPluginId(context.Background(), &query)
err := sqlstore.GetDashboardsByPluginId(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
}
func TestDashboard_SortingOptions(t *testing.T) {
sqlStore := sqlstore.InitTestDB(t)
dashboardStore := ProvideDashboardStore(sqlStore)
// insertTestDashboard uses GoConvey's assertions. Workaround.
t.Run("test with multiple sorting options", func(t *testing.T) {
sqlStore := InitTestDB(t)
dashB := insertTestDashboard(t, sqlStore, "Beta", 1, 0, false)
dashA := insertTestDashboard(t, sqlStore, "Alfa", 1, 0, false)
sqlStore := sqlstore.InitTestDB(t)
dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, false)
dashA := insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, false)
assert.NotZero(t, dashA.Id)
assert.Less(t, dashB.Id, dashA.Id)
q := &search.FindPersistedDashboardsQuery{
@ -464,35 +444,16 @@ func TestDashboard_SortingOptions(t *testing.T) {
searchstore.TitleSorter{Descending: true},
},
}
dashboards, err := sqlStore.findDashboards(context.Background(), q)
dashboards, err := sqlStore.FindDashboards(context.Background(), q)
require.NoError(t, err)
require.Len(t, dashboards, 2)
assert.Equal(t, dashA.Id, dashboards[0].ID)
assert.Equal(t, dashB.Id, dashboards[1].ID)
})
}
func insertTestDashboard(t *testing.T, sqlStore *SQLStore, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
dash, err := sqlStore.SaveDashboard(cmd)
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.Id)
dash.Data.Set("uid", dash.Uid)
return dash
}
func insertTestRule(t *testing.T, sqlStore *SQLStore, foderOrgID int64, folderUID string) {
sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
func insertTestRule(t *testing.T, sqlStore *sqlstore.SQLStore, foderOrgID int64, folderUID string) {
sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
type alertQuery struct {
RefID string
DatasourceUID string
@ -562,26 +523,8 @@ func insertTestRule(t *testing.T, sqlStore *SQLStore, foderOrgID int64, folderUI
return err
})
}
func insertTestDashboardForPlugin(t *testing.T, sqlStore *SQLStore, title string, orgId int64,
folderId int64, isFolder bool, pluginId string) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
}),
PluginId: pluginId,
}
dash, err := sqlStore.SaveDashboard(cmd)
require.NoError(t, err)
return dash
}
func createUser(t *testing.T, sqlStore *SQLStore, name string, role string, isAdmin bool) models.User {
func CreateUser(t *testing.T, sqlStore *sqlstore.SQLStore, name string, role string, isAdmin bool) models.User {
t.Helper()
setting.AutoAssignOrg = true
setting.AutoAssignOrgId = 1
@ -595,3 +538,59 @@ func createUser(t *testing.T, sqlStore *SQLStore, name string, role string, isAd
require.Equal(t, models.RoleType(role), q1.Result[0].Role)
return *currentUser
}
func insertTestDashboard(t *testing.T, dashboardStore *DashboardStore, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
dash, err := dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.Id)
dash.Data.Set("uid", dash.Uid)
return dash
}
func insertTestDashboardForPlugin(t *testing.T, dashboardStore *DashboardStore, title string, orgId int64,
folderId int64, isFolder bool, pluginId string) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
}),
PluginId: pluginId,
}
dash, err := dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
return dash
}
func updateDashboardAcl(t *testing.T, dashboardStore *DashboardStore, dashboardID int64,
items ...models.DashboardAcl) error {
t.Helper()
var itemPtrs []*models.DashboardAcl
for _, it := range items {
item := it
item.Created = time.Now()
item.Updated = time.Now()
itemPtrs = append(itemPtrs, &item)
}
return dashboardStore.UpdateDashboardACL(context.Background(), dashboardID, itemPtrs)
}

View File

@ -1,32 +1,34 @@
//go:build integration
// +build integration
package sqlstore
package database
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/require"
)
func TestDashboardFolderDataAccess(t *testing.T) {
t.Run("Testing DB", func(t *testing.T) {
var sqlStore *SQLStore
var sqlStore *sqlstore.SQLStore
var folder, dashInRoot, childDash *models.Dashboard
var currentUser models.User
var dashboardStore *DashboardStore
setup := func() {
sqlStore = InitTestDB(t)
folder = insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
dashInRoot = insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod", "webapp")
childDash = insertTestDashboard(t, sqlStore, "test dash 23", 1, folder.Id, false, "prod", "webapp")
insertTestDashboard(t, sqlStore, "test dash 45", 1, folder.Id, false, "prod")
currentUser = createUser(t, sqlStore, "viewer", "Viewer", false)
sqlStore = sqlstore.InitTestDB(t)
dashboardStore = ProvideDashboardStore(sqlStore)
folder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod", "webapp")
childDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, folder.Id, false, "prod", "webapp")
insertTestDashboard(t, dashboardStore, "test dash 45", 1, folder.Id, false, "prod")
currentUser = CreateUser(t, sqlStore, "viewer", "Viewer", false)
}
t.Run("Given one dashboard folder with two dashboards and one dashboard in the root folder", func(t *testing.T) {
@ -49,7 +51,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
t.Run("and acl is set for dashboard folder", func(t *testing.T) {
var otherUser int64 = 999
err := testHelperUpdateDashboardAcl(t, sqlStore, folder.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, folder.Id, models.DashboardAcl{
DashboardID: folder.Id,
OrgID: 1,
UserID: otherUser,
@ -70,7 +72,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
t.Run("when the user is given permission", func(t *testing.T) {
err := testHelperUpdateDashboardAcl(t, sqlStore, folder.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, folder.Id, models.DashboardAcl{
DashboardID: folder.Id, OrgID: 1, UserID: currentUser.Id, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
@ -111,9 +113,9 @@ func TestDashboardFolderDataAccess(t *testing.T) {
t.Run("and acl is set for dashboard child and folder has all permissions removed", func(t *testing.T) {
var otherUser int64 = 999
err := testHelperUpdateDashboardAcl(t, sqlStore, folder.Id)
err := updateDashboardAcl(t, dashboardStore, folder.Id)
require.NoError(t, err)
err = testHelperUpdateDashboardAcl(t, sqlStore, childDash.Id, models.DashboardAcl{
err = updateDashboardAcl(t, dashboardStore, childDash.Id, models.DashboardAcl{
DashboardID: folder.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
@ -129,7 +131,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
t.Run("when the user is given permission to child", func(t *testing.T) {
err := testHelperUpdateDashboardAcl(t, sqlStore, childDash.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, childDash.Id, models.DashboardAcl{
DashboardID: childDash.Id, OrgID: 1, UserID: currentUser.Id, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
@ -167,20 +169,21 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
t.Run("Given two dashboard folders with one dashboard each and one dashboard in the root folder", func(t *testing.T) {
var sqlStore *SQLStore
var sqlStore *sqlstore.SQLStore
var folder1, folder2, dashInRoot, childDash1, childDash2 *models.Dashboard
var currentUser models.User
var rootFolderId int64 = 0
setup2 := func() {
sqlStore = InitTestDB(t)
folder1 = insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod")
folder2 = insertTestDashboard(t, sqlStore, "2 test dash folder", 1, 0, true, "prod")
dashInRoot = insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod")
childDash1 = insertTestDashboard(t, sqlStore, "child dash 1", 1, folder1.Id, false, "prod")
childDash2 = insertTestDashboard(t, sqlStore, "child dash 2", 1, folder2.Id, false, "prod")
sqlStore = sqlstore.InitTestDB(t)
dashboardStore := ProvideDashboardStore(sqlStore)
folder1 = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod")
folder2 = insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod")
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod")
childDash1 = insertTestDashboard(t, dashboardStore, "child dash 1", 1, folder1.Id, false, "prod")
childDash2 = insertTestDashboard(t, dashboardStore, "child dash 2", 1, folder2.Id, false, "prod")
currentUser = createUser(t, sqlStore, "viewer", "Viewer", false)
currentUser = CreateUser(t, sqlStore, "viewer", "Viewer", false)
}
setup2()
@ -205,13 +208,13 @@ func TestDashboardFolderDataAccess(t *testing.T) {
t.Run("and acl is set for one dashboard folder", func(t *testing.T) {
const otherUser int64 = 999
err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
t.Run("and a dashboard is moved from folder without acl to the folder with an acl", func(t *testing.T) {
moveDashboard(t, sqlStore, 1, childDash2.Data, folder1.Id)
moveDashboard(t, dashboardStore, 1, childDash2.Data, folder1.Id)
t.Run("should not return folder with acl or its children", func(t *testing.T) {
query := &search.FindPersistedDashboardsQuery{
@ -227,7 +230,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
t.Run("and a dashboard is moved from folder with acl to the folder without an acl", func(t *testing.T) {
setup2()
moveDashboard(t, sqlStore, 1, childDash1.Data, folder2.Id)
moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.Id)
t.Run("should return folder without acl and its children", func(t *testing.T) {
query := &search.FindPersistedDashboardsQuery{
@ -246,12 +249,12 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
t.Run("and a dashboard with an acl is moved to the folder without an acl", func(t *testing.T) {
err := testHelperUpdateDashboardAcl(t, sqlStore, childDash1.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, childDash1.Id, models.DashboardAcl{
DashboardID: childDash1.Id, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
moveDashboard(t, sqlStore, 1, childDash1.Data, folder2.Id)
moveDashboard(t, dashboardStore, 1, childDash1.Data, folder2.Id)
t.Run("should return folder without acl but not the dashboard with acl", func(t *testing.T) {
query := &search.FindPersistedDashboardsQuery{
@ -272,19 +275,20 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
t.Run("Given two dashboard folders", func(t *testing.T) {
var sqlStore *SQLStore
var sqlStore *sqlstore.SQLStore
var folder1, folder2 *models.Dashboard
var adminUser, editorUser, viewerUser models.User
setup3 := func() {
sqlStore = InitTestDB(t)
folder1 = insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod")
folder2 = insertTestDashboard(t, sqlStore, "2 test dash folder", 1, 0, true, "prod")
insertTestDashboard(t, sqlStore, "folder in another org", 2, 0, true, "prod")
sqlStore = sqlstore.InitTestDB(t)
dashboardStore := ProvideDashboardStore(sqlStore)
folder1 = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod")
folder2 = insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod")
insertTestDashboard(t, dashboardStore, "folder in another org", 2, 0, true, "prod")
adminUser = createUser(t, sqlStore, "admin", "Admin", true)
editorUser = createUser(t, sqlStore, "editor", "Editor", false)
viewerUser = createUser(t, sqlStore, "viewer", "Viewer", false)
adminUser = CreateUser(t, sqlStore, "admin", "Admin", true)
editorUser = CreateUser(t, sqlStore, "editor", "Editor", false)
viewerUser = CreateUser(t, sqlStore, "viewer", "Viewer", false)
}
setup3()
@ -313,7 +317,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
OrgRole: models.ROLE_ADMIN,
}
err := GetDashboardPermissionsForUser(context.Background(), &query)
err := sqlstore.GetDashboardPermissionsForUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
@ -336,7 +340,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
query := &models.HasAdminPermissionInFoldersQuery{
SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: models.ROLE_ADMIN},
}
err := HasAdminPermissionInFolders(context.Background(), query)
err := sqlstore.HasAdminPermissionInFolders(context.Background(), query)
require.NoError(t, err)
require.True(t, query.Result)
})
@ -366,7 +370,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
OrgRole: models.ROLE_EDITOR,
}
err := GetDashboardPermissionsForUser(context.Background(), &query)
err := sqlstore.GetDashboardPermissionsForUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
@ -377,7 +381,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
t.Run("Should have write access to one dashboard folder if default role changed to view for one folder", func(t *testing.T) {
err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: editorUser.Id, Permission: models.PERMISSION_VIEW,
})
require.NoError(t, err)
@ -394,7 +398,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
SignedInUser: &models.SignedInUser{UserId: editorUser.Id, OrgId: 1, OrgRole: models.ROLE_EDITOR},
}
err := sqlStore.HasEditPermissionInFolders(context.Background(), query)
require.NoError(t, err)
go require.NoError(t, err)
require.True(t, query.Result)
})
@ -402,7 +406,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
query := &models.HasAdminPermissionInFoldersQuery{
SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: models.ROLE_EDITOR},
}
err := HasAdminPermissionInFolders(context.Background(), query)
err := sqlstore.HasAdminPermissionInFolders(context.Background(), query)
require.NoError(t, err)
require.False(t, query.Result)
})
@ -432,7 +436,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
OrgRole: models.ROLE_VIEWER,
}
err := GetDashboardPermissionsForUser(context.Background(), &query)
err := sqlstore.GetDashboardPermissionsForUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result), 2)
@ -443,7 +447,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
t.Run("Should be able to get one dashboard folder if default role changed to edit for one folder", func(t *testing.T) {
err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
@ -462,7 +466,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
SignedInUser: &models.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
}
err := sqlStore.HasEditPermissionInFolders(context.Background(), query)
require.NoError(t, err)
go require.NoError(t, err)
require.False(t, query.Result)
})
@ -470,13 +474,13 @@ func TestDashboardFolderDataAccess(t *testing.T) {
query := &models.HasAdminPermissionInFoldersQuery{
SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
}
err := HasAdminPermissionInFolders(context.Background(), query)
err := sqlstore.HasAdminPermissionInFolders(context.Background(), query)
require.NoError(t, err)
require.False(t, query.Result)
})
t.Run("and admin permission is given for user with org role viewer in one dashboard folder", func(t *testing.T) {
err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_ADMIN,
})
require.NoError(t, err)
@ -486,13 +490,13 @@ func TestDashboardFolderDataAccess(t *testing.T) {
SignedInUser: &models.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
}
err := sqlStore.HasEditPermissionInFolders(context.Background(), query)
require.NoError(t, err)
go require.NoError(t, err)
require.True(t, query.Result)
})
})
t.Run("and edit permission is given for user with org role viewer in one dashboard folder", func(t *testing.T) {
err := testHelperUpdateDashboardAcl(t, sqlStore, folder1.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, folder1.Id, models.DashboardAcl{
DashboardID: folder1.Id, OrgID: 1, UserID: viewerUser.Id, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
@ -502,7 +506,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
SignedInUser: &models.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
}
err := sqlStore.HasEditPermissionInFolders(context.Background(), query)
require.NoError(t, err)
go require.NoError(t, err)
require.True(t, query.Result)
})
})
@ -512,15 +516,16 @@ func TestDashboardFolderDataAccess(t *testing.T) {
t.Run("Given dashboard and folder with the same title", func(t *testing.T) {
var orgId int64 = 1
title := "Very Unique Name"
var sqlStore *SQLStore
var sqlStore *sqlstore.SQLStore
var folder1, folder2 *models.Dashboard
sqlStore = InitTestDB(t)
folder2 = insertTestDashboard(t, sqlStore, "TEST", orgId, 0, true, "prod")
_ = insertTestDashboard(t, sqlStore, title, orgId, folder2.Id, false, "prod")
folder1 = insertTestDashboard(t, sqlStore, title, orgId, 0, true, "prod")
sqlStore = sqlstore.InitTestDB(t)
dashboardStore := ProvideDashboardStore(sqlStore)
folder2 = insertTestDashboard(t, dashboardStore, "TEST", orgId, 0, true, "prod")
_ = insertTestDashboard(t, dashboardStore, title, orgId, folder2.Id, false, "prod")
folder1 = insertTestDashboard(t, dashboardStore, title, orgId, 0, true, "prod")
t.Run("GetFolderByTitle should find the folder", func(t *testing.T) {
result, err := sqlStore.GetFolderByTitle(orgId, title)
result, err := dashboardStore.GetFolderByTitle(orgId, title)
require.NoError(t, err)
require.Equal(t, folder1.Id, result.Id)
})
@ -528,7 +533,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
})
}
func moveDashboard(t *testing.T, sqlStore *SQLStore, orgId int64, dashboard *simplejson.Json,
func moveDashboard(t *testing.T, dashboardStore *DashboardStore, orgId int64, dashboard *simplejson.Json,
newFolderId int64) *models.Dashboard {
t.Helper()
@ -538,7 +543,7 @@ func moveDashboard(t *testing.T, sqlStore *SQLStore, orgId int64, dashboard *sim
Dashboard: dashboard,
Overwrite: true,
}
dash, err := sqlStore.SaveDashboard(cmd)
dash, err := dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
return dash

View File

@ -0,0 +1,217 @@
// Code generated by mockery v2.10.0. DO NOT EDIT.
package database
import (
context "context"
mock "github.com/stretchr/testify/mock"
models "github.com/grafana/grafana/pkg/models"
)
// FakeDashboardStore is an autogenerated mock type for the Store type
type FakeDashboardStore struct {
mock.Mock
}
// GetFolderByTitle provides a mock function with given fields: orgID, title
func (_m *FakeDashboardStore) GetFolderByTitle(orgID int64, title string) (*models.Dashboard, error) {
ret := _m.Called(orgID, title)
var r0 *models.Dashboard
if rf, ok := ret.Get(0).(func(int64, string) *models.Dashboard); ok {
r0 = rf(orgID, title)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Dashboard)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(int64, string) error); ok {
r1 = rf(orgID, title)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetProvisionedDashboardData provides a mock function with given fields: name
func (_m *FakeDashboardStore) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
ret := _m.Called(name)
var r0 []*models.DashboardProvisioning
if rf, ok := ret.Get(0).(func(string) []*models.DashboardProvisioning); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.DashboardProvisioning)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetProvisionedDataByDashboardID provides a mock function with given fields: dashboardID
func (_m *FakeDashboardStore) GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
ret := _m.Called(dashboardID)
var r0 *models.DashboardProvisioning
if rf, ok := ret.Get(0).(func(int64) *models.DashboardProvisioning); ok {
r0 = rf(dashboardID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.DashboardProvisioning)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(int64) error); ok {
r1 = rf(dashboardID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetProvisionedDataByDashboardUID provides a mock function with given fields: orgID, dashboardUID
func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error) {
ret := _m.Called(orgID, dashboardUID)
var r0 *models.DashboardProvisioning
if rf, ok := ret.Get(0).(func(int64, string) *models.DashboardProvisioning); ok {
r0 = rf(orgID, dashboardUID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.DashboardProvisioning)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(int64, string) error); ok {
r1 = rf(orgID, dashboardUID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SaveAlerts provides a mock function with given fields: ctx, dashID, alerts
func (_m *FakeDashboardStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error {
ret := _m.Called(ctx, dashID, alerts)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Alert) error); ok {
r0 = rf(ctx, dashID, alerts)
} else {
r0 = ret.Error(0)
}
return r0
}
// SaveDashboard provides a mock function with given fields: cmd
func (_m *FakeDashboardStore) SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) {
ret := _m.Called(cmd)
var r0 *models.Dashboard
if rf, ok := ret.Get(0).(func(models.SaveDashboardCommand) *models.Dashboard); ok {
r0 = rf(cmd)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Dashboard)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(models.SaveDashboardCommand) error); ok {
r1 = rf(cmd)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SaveProvisionedDashboard provides a mock function with given fields: cmd, provisioning
func (_m *FakeDashboardStore) SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
ret := _m.Called(cmd, provisioning)
var r0 *models.Dashboard
if rf, ok := ret.Get(0).(func(models.SaveDashboardCommand, *models.DashboardProvisioning) *models.Dashboard); ok {
r0 = rf(cmd, provisioning)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Dashboard)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(models.SaveDashboardCommand, *models.DashboardProvisioning) error); ok {
r1 = rf(cmd, provisioning)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UnprovisionDashboard provides a mock function with given fields: ctx, id
func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error {
ret := _m.Called(ctx, id)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateDashboardACL provides a mock function with given fields: ctx, uid, items
func (_m *FakeDashboardStore) UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error {
ret := _m.Called(ctx, uid, items)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.DashboardAcl) error); ok {
r0 = rf(ctx, uid, items)
} else {
r0 = ret.Error(0)
}
return r0
}
// ValidateDashboardBeforeSave provides a mock function with given fields: dashboard, overwrite
func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
ret := _m.Called(dashboard, overwrite)
var r0 bool
if rf, ok := ret.Get(0).(func(*models.Dashboard, bool) bool); ok {
r0 = rf(dashboard, overwrite)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(*models.Dashboard, bool) error); ok {
r1 = rf(dashboard, overwrite)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -1,10 +1,11 @@
//go:build integration
// +build integration
package sqlstore
package database
import (
"context"
"github.com/grafana/grafana/pkg/services/sqlstore"
"testing"
"time"
@ -15,7 +16,8 @@ import (
)
func TestDashboardProvisioningTest(t *testing.T) {
sqlStore := InitTestDB(t)
sqlStore := sqlstore.InitTestDB(t)
dashboardStore := ProvideDashboardStore(sqlStore)
folderCmd := models.SaveDashboardCommand{
OrgId: 1,
@ -27,7 +29,7 @@ func TestDashboardProvisioningTest(t *testing.T) {
}),
}
dash, err := sqlStore.SaveDashboard(folderCmd)
dash, err := dashboardStore.SaveDashboard(folderCmd)
require.Nil(t, err)
saveDashboardCmd := models.SaveDashboardCommand{
@ -49,7 +51,7 @@ func TestDashboardProvisioningTest(t *testing.T) {
Updated: now.Unix(),
}
dash, err := sqlStore.SaveProvisionedDashboard(saveDashboardCmd, provisioning)
dash, err := dashboardStore.SaveProvisionedDashboard(saveDashboardCmd, provisioning)
require.Nil(t, err)
require.NotNil(t, dash)
require.NotEqual(t, 0, dash.Id)
@ -71,7 +73,7 @@ func TestDashboardProvisioningTest(t *testing.T) {
Updated: now.Unix(),
}
anotherDash, err := sqlStore.SaveProvisionedDashboard(saveCmd, provisioning)
anotherDash, err := dashboardStore.SaveProvisionedDashboard(saveCmd, provisioning)
require.Nil(t, err)
query := &models.GetDashboardsQuery{DashboardIds: []int64{anotherDash.Id}}
@ -91,7 +93,7 @@ func TestDashboardProvisioningTest(t *testing.T) {
})
t.Run("Can query for provisioned dashboards", func(t *testing.T) {
rslt, err := sqlStore.GetProvisionedDashboardData("default")
rslt, err := dashboardStore.GetProvisionedDashboardData("default")
require.Nil(t, err)
require.Equal(t, 1, len(rslt))
@ -100,13 +102,13 @@ func TestDashboardProvisioningTest(t *testing.T) {
})
t.Run("Can query for one provisioned dashboard", func(t *testing.T) {
data, err := sqlStore.GetProvisionedDataByDashboardID(dash.Id)
data, err := dashboardStore.GetProvisionedDataByDashboardID(dash.Id)
require.Nil(t, err)
require.NotNil(t, data)
})
t.Run("Can query for none provisioned dashboard", func(t *testing.T) {
data, err := sqlStore.GetProvisionedDataByDashboardID(3000)
data, err := dashboardStore.GetProvisionedDataByDashboardID(3000)
require.Nil(t, err)
require.Nil(t, data)
})
@ -119,19 +121,15 @@ func TestDashboardProvisioningTest(t *testing.T) {
require.Nil(t, sqlStore.DeleteDashboard(context.Background(), deleteCmd))
data, err := sqlStore.GetProvisionedDataByDashboardID(dash.Id)
data, err := dashboardStore.GetProvisionedDataByDashboardID(dash.Id)
require.Nil(t, err)
require.Nil(t, data)
})
t.Run("UnprovisionDashboard should delete provisioning metadata", func(t *testing.T) {
unprovisionCmd := &models.UnprovisionDashboardCommand{
Id: dashId,
}
require.Nil(t, dashboardStore.UnprovisionDashboard(context.Background(), dashId))
require.Nil(t, UnprovisionDashboard(context.Background(), unprovisionCmd))
data, err := sqlStore.GetProvisionedDataByDashboardID(dashId)
data, err := dashboardStore.GetProvisionedDataByDashboardID(dashId)
require.Nil(t, err)
require.Nil(t, data)
})

View File

@ -1,11 +1,16 @@
//go:build integration
// +build integration
package sqlstore
package permissions
import (
"context"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"testing"
"time"
"github.com/grafana/grafana/pkg/models"
@ -13,20 +18,22 @@ import (
)
func TestDashboardAclDataAccess(t *testing.T) {
var sqlStore *SQLStore
var sqlStore *sqlstore.SQLStore
var currentUser models.User
var savedFolder, childDash *models.Dashboard
var dashboardStore *database.DashboardStore
setup := func(t *testing.T) {
sqlStore = InitTestDB(t)
sqlStore = sqlstore.InitTestDB(t)
dashboardStore = database.ProvideDashboardStore(sqlStore)
currentUser = createUser(t, sqlStore, "viewer", "Viewer", false)
savedFolder = insertTestDashboard(t, sqlStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
childDash = insertTestDashboard(t, sqlStore, "2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
childDash = insertTestDashboard(t, dashboardStore, "2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
}
t.Run("Dashboard permission with userId and teamId set to 0", func(t *testing.T) {
setup(t)
err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1,
DashboardID: savedFolder.Id,
Permission: models.PERMISSION_EDIT,
@ -70,7 +77,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
t.Run("Folder with removed default permissions returns no acl items", func(t *testing.T) {
setup(t)
err := sqlStore.UpdateDashboardACL(context.Background(), savedFolder.Id, nil)
err := dashboardStore.UpdateDashboardACL(context.Background(), savedFolder.Id, nil)
require.Nil(t, err)
query := models.GetDashboardAclInfoListQuery{DashboardID: childDash.Id, OrgID: 1}
@ -84,7 +91,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
t.Run("Given dashboard folder permission", func(t *testing.T) {
setup(t)
err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1,
UserID: currentUser.Id,
DashboardID: savedFolder.Id,
@ -103,7 +110,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
t.Run("Given child dashboard permission", func(t *testing.T) {
err := testHelperUpdateDashboardAcl(t, sqlStore, childDash.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, childDash.Id, models.DashboardAcl{
OrgID: 1,
UserID: currentUser.Id,
DashboardID: childDash.Id,
@ -128,7 +135,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
t.Run("Reading dashboard acl should include default acl for parent folder and the child acl", func(t *testing.T) {
setup(t)
err := testHelperUpdateDashboardAcl(t, sqlStore, childDash.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, childDash.Id, models.DashboardAcl{
OrgID: 1,
UserID: currentUser.Id,
DashboardID: childDash.Id,
@ -155,7 +162,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
t.Run("Add and delete dashboard permission", func(t *testing.T) {
setup(t)
err := testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
err := updateDashboardAcl(t, dashboardStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1,
UserID: currentUser.Id,
DashboardID: savedFolder.Id,
@ -174,7 +181,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
require.Equal(t, currentUser.Login, q1.Result[0].UserLogin)
require.Equal(t, currentUser.Email, q1.Result[0].UserEmail)
err = testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id)
err = updateDashboardAcl(t, dashboardStore, savedFolder.Id)
require.Nil(t, err)
q3 := &models.GetDashboardAclInfoListQuery{DashboardID: savedFolder.Id, OrgID: 1}
@ -188,7 +195,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
team1, err := sqlStore.CreateTeam("group1 name", "", 1)
require.Nil(t, err)
err = testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
err = updateDashboardAcl(t, dashboardStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1,
TeamID: team1.Id,
DashboardID: savedFolder.Id,
@ -208,7 +215,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
setup(t)
team1, err := sqlStore.CreateTeam("group1 name", "", 1)
require.Nil(t, err)
err = testHelperUpdateDashboardAcl(t, sqlStore, savedFolder.Id, models.DashboardAcl{
err = updateDashboardAcl(t, dashboardStore, savedFolder.Id, models.DashboardAcl{
OrgID: 1,
TeamID: team1.Id,
DashboardID: savedFolder.Id,
@ -229,7 +236,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
t.Run("Default permissions for root folder dashboards", func(t *testing.T) {
setup(t)
var rootFolderId int64 = 0
sqlStore := InitTestDB(t)
sqlStore := sqlstore.InitTestDB(t)
query := models.GetDashboardAclInfoListQuery{DashboardID: rootFolderId, OrgID: 1}
@ -246,3 +253,54 @@ func TestDashboardAclDataAccess(t *testing.T) {
require.False(t, query.Result[1].Inherited)
})
}
func createUser(t *testing.T, sqlStore *sqlstore.SQLStore, name string, role string, isAdmin bool) models.User {
t.Helper()
setting.AutoAssignOrg = true
setting.AutoAssignOrgId = 1
setting.AutoAssignOrgRole = role
currentUserCmd := models.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin}
currentUser, err := sqlStore.CreateUser(context.Background(), currentUserCmd)
require.NoError(t, err)
q1 := models.GetUserOrgListQuery{UserId: currentUser.Id}
err = sqlStore.GetUserOrgList(context.Background(), &q1)
require.NoError(t, err)
require.Equal(t, models.RoleType(role), q1.Result[0].Role)
return *currentUser
}
func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
dash, err := dashboardStore.SaveDashboard(cmd)
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.Id)
dash.Data.Set("uid", dash.Uid)
return dash
}
func updateDashboardAcl(t *testing.T, dashboardStore *database.DashboardStore, dashboardID int64,
items ...models.DashboardAcl) error {
t.Helper()
var itemPtrs []*models.DashboardAcl
for _, it := range items {
item := it
item.Created = time.Now()
item.Updated = time.Now()
itemPtrs = append(itemPtrs, &item)
}
return dashboardStore.UpdateDashboardACL(context.Background(), dashboardID, itemPtrs)
}

View File

@ -0,0 +1,20 @@
package dashboards
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
//go:generate mockery --name FolderService --structname FakeFolderService --inpackage --filename folder_service_mock.go
// FolderService is a service for operating on folders.
type FolderService interface {
GetFolders(ctx context.Context, user *models.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error)
GetFolderByID(ctx context.Context, user *models.SignedInUser, id int64, orgID int64) (*models.Folder, error)
GetFolderByUID(ctx context.Context, user *models.SignedInUser, orgID int64, uid string) (*models.Folder, error)
GetFolderByTitle(ctx context.Context, user *models.SignedInUser, orgID int64, title string) (*models.Folder, error)
CreateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, title, uid string) (*models.Folder, error)
UpdateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error
DeleteFolder(ctx context.Context, user *models.SignedInUser, orgID int64, uid string, forceDeleteRules bool) (*models.Folder, error)
MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
}

View File

@ -0,0 +1,181 @@
// Code generated by mockery v2.10.0. DO NOT EDIT.
package dashboards
import (
context "context"
models "github.com/grafana/grafana/pkg/models"
mock "github.com/stretchr/testify/mock"
)
// FakeFolderService is an autogenerated mock type for the FolderService type
type FakeFolderService struct {
mock.Mock
}
// CreateFolder provides a mock function with given fields: ctx, user, orgID, title, uid
func (_m *FakeFolderService) CreateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, title string, uid string) (*models.Folder, error) {
ret := _m.Called(ctx, user, orgID, title, uid)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, *models.SignedInUser, int64, string, string) *models.Folder); ok {
r0 = rf(ctx, user, orgID, title, uid)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.SignedInUser, int64, string, string) error); ok {
r1 = rf(ctx, user, orgID, title, uid)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteFolder provides a mock function with given fields: ctx, user, orgID, uid, forceDeleteRules
func (_m *FakeFolderService) DeleteFolder(ctx context.Context, user *models.SignedInUser, orgID int64, uid string, forceDeleteRules bool) (*models.Folder, error) {
ret := _m.Called(ctx, user, orgID, uid, forceDeleteRules)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, *models.SignedInUser, int64, string, bool) *models.Folder); ok {
r0 = rf(ctx, user, orgID, uid, forceDeleteRules)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.SignedInUser, int64, string, bool) error); ok {
r1 = rf(ctx, user, orgID, uid, forceDeleteRules)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetFolderByID provides a mock function with given fields: ctx, user, id, orgID
func (_m *FakeFolderService) GetFolderByID(ctx context.Context, user *models.SignedInUser, id int64, orgID int64) (*models.Folder, error) {
ret := _m.Called(ctx, user, id, orgID)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, *models.SignedInUser, int64, int64) *models.Folder); ok {
r0 = rf(ctx, user, id, orgID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.SignedInUser, int64, int64) error); ok {
r1 = rf(ctx, user, id, orgID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetFolderByTitle provides a mock function with given fields: ctx, user, orgID, title
func (_m *FakeFolderService) GetFolderByTitle(ctx context.Context, user *models.SignedInUser, orgID int64, title string) (*models.Folder, error) {
ret := _m.Called(ctx, user, orgID, title)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, *models.SignedInUser, int64, string) *models.Folder); ok {
r0 = rf(ctx, user, orgID, title)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.SignedInUser, int64, string) error); ok {
r1 = rf(ctx, user, orgID, title)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetFolderByUID provides a mock function with given fields: ctx, user, orgID, uid
func (_m *FakeFolderService) GetFolderByUID(ctx context.Context, user *models.SignedInUser, orgID int64, uid string) (*models.Folder, error) {
ret := _m.Called(ctx, user, orgID, uid)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, *models.SignedInUser, int64, string) *models.Folder); ok {
r0 = rf(ctx, user, orgID, uid)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.SignedInUser, int64, string) error); ok {
r1 = rf(ctx, user, orgID, uid)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetFolders provides a mock function with given fields: ctx, user, orgID, limit, page
func (_m *FakeFolderService) GetFolders(ctx context.Context, user *models.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
ret := _m.Called(ctx, user, orgID, limit, page)
var r0 []*models.Folder
if rf, ok := ret.Get(0).(func(context.Context, *models.SignedInUser, int64, int64, int64) []*models.Folder); ok {
r0 = rf(ctx, user, orgID, limit, page)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *models.SignedInUser, int64, int64, int64) error); ok {
r1 = rf(ctx, user, orgID, limit, page)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MakeUserAdmin provides a mock function with given fields: ctx, orgID, userID, folderID, setViewAndEditPermissions
func (_m *FakeFolderService) MakeUserAdmin(ctx context.Context, orgID int64, userID int64, folderID int64, setViewAndEditPermissions bool) error {
ret := _m.Called(ctx, orgID, userID, folderID, setViewAndEditPermissions)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, int64, int64, bool) error); ok {
r0 = rf(ctx, orgID, userID, folderID, setViewAndEditPermissions)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateFolder provides a mock function with given fields: ctx, user, orgID, existingUid, cmd
func (_m *FakeFolderService) UpdateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
ret := _m.Called(ctx, user, orgID, existingUid, cmd)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.SignedInUser, int64, string, *models.UpdateFolderCommand) error); ok {
r0 = rf(ctx, user, orgID, existingUid, cmd)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -1,4 +1,4 @@
package dashboards
package service
import (
"context"
@ -7,86 +7,42 @@ import (
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
m "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil"
)
// DashboardService is a service for operating on dashboards.
type DashboardService interface {
SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error)
ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error)
DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error
MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error
type DashboardServiceImpl struct {
dashboardStore m.Store
log log.Logger
}
// DashboardProvisioningService is a service for operating on provisioned dashboards.
type DashboardProvisioningService interface {
SaveProvisionedDashboard(ctx context.Context, dto *SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
SaveFolderForProvisionedDashboards(context.Context, *SaveDashboardDTO) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error)
GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
UnprovisionDashboard(ctx context.Context, dashboardID int64) error
DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error
}
// NewService is a factory for creating a new dashboard service.
var NewService = func(store dashboards.Store) DashboardService {
return &dashboardServiceImpl{
func ProvideDashboardService(store m.Store) *DashboardServiceImpl {
return &DashboardServiceImpl{
dashboardStore: store,
log: log.New("dashboard-service"),
}
}
// NewProvisioningService is a factory for creating a new dashboard provisioning service.
var NewProvisioningService = func(store dashboards.Store) DashboardProvisioningService {
return NewService(store).(*dashboardServiceImpl)
}
type SaveDashboardDTO struct {
OrgId int64
UpdatedAt time.Time
User *models.SignedInUser
Message string
Overwrite bool
Dashboard *models.Dashboard
}
type dashboardServiceImpl struct {
dashboardStore dashboards.Store
orgId int64
user *models.SignedInUser
log log.Logger
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
func (dr *DashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
return dr.dashboardStore.GetProvisionedDashboardData(name)
}
// GetProvisionedData gets provisioned dashboard data.
//
// Stubbable by tests.
var GetProvisionedData = func(store dashboards.Store, dashboardID int64) (*models.DashboardProvisioning, error) {
return store.GetProvisionedDataByDashboardID(dashboardID)
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
return dr.dashboardStore.GetProvisionedDataByDashboardID(dashboardID)
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
return GetProvisionedData(dr.dashboardStore, dashboardID)
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error) {
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error) {
return dr.dashboardStore.GetProvisionedDataByDashboardUID(orgID, dashboardUID)
}
func (dr *dashboardServiceImpl) buildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool,
func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, dto *m.SaveDashboardDTO, shouldValidateAlerts bool,
validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) {
dash := dto.Dashboard
@ -175,6 +131,10 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(ctx context.Context, d
return cmd, nil
}
func (dr *DashboardServiceImpl) UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error {
return dr.dashboardStore.UpdateDashboardACL(ctx, uid, items)
}
var validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
extractor := alerting.NewDashAlertExtractor(dash, dash.OrgId, user)
return extractor.ValidateAlerts(ctx)
@ -210,7 +170,7 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error {
// UpdateAlerting updates alerting.
//
// Stubbable by tests.
var UpdateAlerting = func(ctx context.Context, store dashboards.Store, orgID int64, dashboard *models.Dashboard, user *models.SignedInUser) error {
var UpdateAlerting = func(ctx context.Context, store m.Store, orgID int64, dashboard *models.Dashboard, user *models.SignedInUser) error {
extractor := alerting.NewDashAlertExtractor(dashboard, orgID, user)
alerts, err := extractor.GetAlerts(ctx)
if err != nil {
@ -220,7 +180,7 @@ var UpdateAlerting = func(ctx context.Context, store dashboards.Store, orgID int
return store.SaveAlerts(ctx, dashboard.Id, alerts)
}
func (dr *dashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dto *SaveDashboardDTO,
func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dto *m.SaveDashboardDTO,
provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil {
dr.log.Warn("Changing refresh interval for provisioned dashboard to minimum refresh interval", "dashboardUid",
@ -234,7 +194,7 @@ func (dr *dashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
OrgId: dto.OrgId,
}
cmd, err := dr.buildSaveDashboardCommand(ctx, dto, true, false)
cmd, err := dr.BuildSaveDashboardCommand(ctx, dto, true, false)
if err != nil {
return nil, err
}
@ -253,12 +213,12 @@ func (dr *dashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
return dash, nil
}
func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) {
func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.Context, dto *m.SaveDashboardDTO) (*models.Dashboard, error) {
dto.User = &models.SignedInUser{
UserId: 0,
OrgRole: models.ROLE_ADMIN,
}
cmd, err := dr.buildSaveDashboardCommand(ctx, dto, false, false)
cmd, err := dr.BuildSaveDashboardCommand(ctx, dto, false, false)
if err != nil {
return nil, err
}
@ -275,7 +235,7 @@ func (dr *dashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C
return dash, nil
}
func (dr *dashboardServiceImpl) SaveDashboard(ctx context.Context, dto *SaveDashboardDTO,
func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *m.SaveDashboardDTO,
allowUiUpdate bool) (*models.Dashboard, error) {
if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil {
dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval",
@ -284,7 +244,7 @@ func (dr *dashboardServiceImpl) SaveDashboard(ctx context.Context, dto *SaveDash
dto.Dashboard.Data.Set("refresh", setting.MinRefreshInterval)
}
cmd, err := dr.buildSaveDashboardCommand(ctx, dto, true, !allowUiUpdate)
cmd, err := dr.BuildSaveDashboardCommand(ctx, dto, true, !allowUiUpdate)
if err != nil {
return nil, err
}
@ -303,16 +263,59 @@ func (dr *dashboardServiceImpl) SaveDashboard(ctx context.Context, dto *SaveDash
// DeleteDashboard removes dashboard from the DB. Errors out if the dashboard was provisioned. Should be used for
// operations by the user where we want to make sure user does not delete provisioned dashboard.
func (dr *dashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
return dr.deleteDashboard(ctx, dashboardId, orgId, true)
}
func (dr *DashboardServiceImpl) MakeUserAdmin(ctx context.Context, orgID int64, userID int64, dashboardID int64, setViewAndEditPermissions bool) error {
rtEditor := models.ROLE_EDITOR
rtViewer := models.ROLE_VIEWER
items := []*models.DashboardAcl{
{
OrgID: orgID,
DashboardID: dashboardID,
UserID: userID,
Permission: models.PERMISSION_ADMIN,
Created: time.Now(),
Updated: time.Now(),
},
}
if setViewAndEditPermissions {
items = append(items,
&models.DashboardAcl{
OrgID: orgID,
DashboardID: dashboardID,
Role: &rtEditor,
Permission: models.PERMISSION_EDIT,
Created: time.Now(),
Updated: time.Now(),
},
&models.DashboardAcl{
OrgID: orgID,
DashboardID: dashboardID,
Role: &rtViewer,
Permission: models.PERMISSION_VIEW,
Created: time.Now(),
Updated: time.Now(),
},
)
}
if err := dr.dashboardStore.UpdateDashboardACL(ctx, dashboardID, items); err != nil {
return err
}
return nil
}
// DeleteProvisionedDashboard removes dashboard from the DB even if it is provisioned.
func (dr *dashboardServiceImpl) DeleteProvisionedDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
func (dr *DashboardServiceImpl) DeleteProvisionedDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
return dr.deleteDashboard(ctx, dashboardId, orgId, false)
}
func (dr *dashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId int64, orgId int64, validateProvisionedDashboard bool) error {
func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId int64, orgId int64, validateProvisionedDashboard bool) error {
if validateProvisionedDashboard {
provisionedData, err := dr.GetProvisionedDashboardDataByDashboardID(dashboardId)
if err != nil {
@ -327,7 +330,7 @@ func (dr *dashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId
return bus.Dispatch(ctx, cmd)
}
func (dr *dashboardServiceImpl) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (
func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *m.SaveDashboardDTO) (
*models.Dashboard, error) {
if err := validateDashboardRefreshInterval(dto.Dashboard); err != nil {
dr.log.Warn("Changing refresh interval for imported dashboard to minimum refresh interval",
@ -336,7 +339,7 @@ func (dr *dashboardServiceImpl) ImportDashboard(ctx context.Context, dto *SaveDa
dto.Dashboard.Data.Set("refresh", setting.MinRefreshInterval)
}
cmd, err := dr.buildSaveDashboardCommand(ctx, dto, false, true)
cmd, err := dr.BuildSaveDashboardCommand(ctx, dto, false, true)
if err != nil {
return nil, err
}
@ -351,50 +354,6 @@ func (dr *dashboardServiceImpl) ImportDashboard(ctx context.Context, dto *SaveDa
// UnprovisionDashboard removes info about dashboard being provisioned. Used after provisioning configs are changed
// and provisioned dashboards are left behind but not deleted.
func (dr *dashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashboardId int64) error {
cmd := &models.UnprovisionDashboardCommand{Id: dashboardId}
return bus.Dispatch(ctx, cmd)
}
type FakeDashboardService struct {
DashboardService
SaveDashboardResult *models.Dashboard
SaveDashboardError error
SavedDashboards []*SaveDashboardDTO
ProvisionedDashData *models.DashboardProvisioning
}
func (s *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) {
s.SavedDashboards = append(s.SavedDashboards, dto)
if s.SaveDashboardResult == nil && s.SaveDashboardError == nil {
s.SaveDashboardResult = dto.Dashboard
}
return s.SaveDashboardResult, s.SaveDashboardError
}
func (s *FakeDashboardService) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) {
return s.SaveDashboard(ctx, dto, true)
}
func (s *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
for index, dash := range s.SavedDashboards {
if dash.Dashboard.Id == dashboardId && dash.OrgId == orgId {
s.SavedDashboards = append(s.SavedDashboards[:index], s.SavedDashboards[index+1:]...)
break
}
}
return nil
}
func (s *FakeDashboardService) GetProvisionedDashboardDataByDashboardID(id int64) (*models.DashboardProvisioning, error) {
return s.ProvisionedDashData, nil
}
func MockDashboardService(mock *FakeDashboardService) {
NewService = func(dashboards.Store) DashboardService {
return mock
}
func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashboardId int64) error {
return dr.dashboardStore.UnprovisionDashboard(ctx, dashboardId)
}

View File

@ -1,20 +1,20 @@
//go:build integration
// +build integration
package dashboards
package service
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
dashbboardservice "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
)
const testOrgID int64 = 1
@ -25,7 +25,7 @@ func TestIntegratedDashboardService(t *testing.T) {
t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
})
UpdateAlerting = func(ctx context.Context, store dashboards.Store, orgID int64, dashboard *models.Dashboard, user *models.SignedInUser) error {
UpdateAlerting = func(ctx context.Context, store dashbboardservice.Store, orgID int64, dashboard *models.Dashboard, user *models.SignedInUser) error {
return nil
}
@ -860,7 +860,8 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore
t.Helper()
dto := toSaveDashboardDto(cmd)
res, err := NewService(sqlStore).SaveDashboard(context.Background(), &dto, false)
dashboardStore := database.ProvideDashboardStore(sqlStore)
res, err := ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err)
return res
@ -868,7 +869,8 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore
func callSaveWithError(cmd models.SaveDashboardCommand, sqlStore *sqlstore.SQLStore) error {
dto := toSaveDashboardDto(cmd)
_, err := NewService(sqlStore).SaveDashboard(context.Background(), &dto, false)
dashboardStore := database.ProvideDashboardStore(sqlStore)
_, err := ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), &dto, false)
return err
}
@ -885,7 +887,7 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto
}),
}
dto := SaveDashboardDTO{
dto := dashbboardservice.SaveDashboardDTO{
OrgId: orgID,
Dashboard: cmd.GetDashboardModel(),
User: &models.SignedInUser{
@ -894,7 +896,8 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto
},
}
res, err := NewService(sqlStore).SaveDashboard(context.Background(), &dto, false)
dashboardStore := database.ProvideDashboardStore(sqlStore)
res, err := ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err)
return res
@ -912,7 +915,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore.
}),
}
dto := SaveDashboardDTO{
dto := dashbboardservice.SaveDashboardDTO{
OrgId: orgID,
Dashboard: cmd.GetDashboardModel(),
User: &models.SignedInUser{
@ -921,16 +924,17 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore.
},
}
res, err := NewService(sqlStore).SaveDashboard(context.Background(), &dto, false)
dashboardStore := database.ProvideDashboardStore(sqlStore)
res, err := ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), &dto, false)
require.NoError(t, err)
return res
}
func toSaveDashboardDto(cmd models.SaveDashboardCommand) SaveDashboardDTO {
func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashbboardservice.SaveDashboardDTO {
dash := (&cmd).GetDashboardModel()
return SaveDashboardDTO{
return dashbboardservice.SaveDashboardDTO{
Dashboard: dash,
Message: cmd.Message,
OrgId: cmd.OrgId,

View File

@ -1,18 +1,22 @@
package dashboards
//go:build integration
// +build integration
package service
import (
"context"
"fmt"
"errors"
"testing"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
m "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
@ -20,8 +24,9 @@ func TestDashboardService(t *testing.T) {
t.Run("Dashboard service tests", func(t *testing.T) {
bus.ClearBusHandlers()
fakeStore := fakeDashboardStore{}
service := &dashboardServiceImpl{
fakeStore := database.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{
log: log.New("test.logger"),
dashboardStore: &fakeStore,
}
@ -31,7 +36,7 @@ func TestDashboardService(t *testing.T) {
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
t.Run("Save dashboard validation", func(t *testing.T) {
dto := &SaveDashboardDTO{}
dto := &m.SaveDashboardDTO{}
t.Run("When saving a dashboard with empty title it should return error", func(t *testing.T) {
titles := []string{"", " ", " \t "}
@ -57,14 +62,6 @@ func TestDashboardService(t *testing.T) {
})
t.Run("When saving a dashboard should validate uid", func(t *testing.T) {
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
return nil
}
testCases := []struct {
Uid string
Error error
@ -83,24 +80,17 @@ func TestDashboardService(t *testing.T) {
dto.Dashboard.SetUid(tc.Uid)
dto.User = &models.SignedInUser{}
_, err := service.buildSaveDashboardCommand(context.Background(), dto, true, false)
if tc.Error == nil {
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once()
}
_, err := service.BuildSaveDashboardCommand(context.Background(), dto, true, false)
require.Equal(t, err, tc.Error)
}
})
t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) {
t.Cleanup(func() {
fakeStore.provisionedData = nil
})
fakeStore.provisionedData = &models.DashboardProvisioning{}
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
return nil
}
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once()
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{}, nil).Once()
dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3)
@ -110,13 +100,9 @@ func TestDashboardService(t *testing.T) {
})
t.Run("Should not return validation error if dashboard is provisioned but UI updates allowed", func(t *testing.T) {
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
return nil
}
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once()
fakeStore.On("SaveDashboard", mock.Anything).Return(&models.Dashboard{Data: simplejson.New()}, nil).Once()
fakeStore.On("SaveAlerts", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3)
@ -126,40 +112,25 @@ func TestDashboardService(t *testing.T) {
})
t.Run("Should return validation error if alert data is invalid", func(t *testing.T) {
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
return fmt.Errorf("alert validation error")
}
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once()
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(nil, nil).Once()
fakeStore.On("SaveDashboard", mock.Anything).Return(&models.Dashboard{Data: simplejson.New()}, nil).Once()
fakeStore.On("SaveAlerts", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("alert validation error")).Once()
dto.Dashboard = models.NewDashboard("Dash")
dto.User = &models.SignedInUser{UserId: 1}
_, err := service.SaveDashboard(context.Background(), dto, false)
require.Equal(t, err.Error(), "alert validation error")
})
})
t.Run("Save provisioned dashboard validation", func(t *testing.T) {
dto := &SaveDashboardDTO{}
dto := &m.SaveDashboardDTO{}
t.Run("Should not return validation error if dashboard is provisioned", func(t *testing.T) {
origUpdateAlerting := UpdateAlerting
t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
})
UpdateAlerting = func(ctx context.Context, store dashboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
}
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
return nil
}
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once()
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything).Return(&models.Dashboard{Data: simplejson.New()}, nil).Once()
fakeStore.On("SaveAlerts", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3)
@ -169,27 +140,14 @@ func TestDashboardService(t *testing.T) {
})
t.Run("Should override invalid refresh interval if dashboard is provisioned", func(t *testing.T) {
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once()
fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything).Return(&models.Dashboard{Data: simplejson.New()}, nil).Once()
fakeStore.On("SaveAlerts", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
oldRefreshInterval := setting.MinRefreshInterval
setting.MinRefreshInterval = "5m"
defer func() { setting.MinRefreshInterval = oldRefreshInterval }()
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
return nil
}
origUpdateAlerting := UpdateAlerting
t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
})
UpdateAlerting = func(ctx context.Context, store dashboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
}
dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1}
@ -201,30 +159,11 @@ func TestDashboardService(t *testing.T) {
})
t.Run("Import dashboard validation", func(t *testing.T) {
dto := &SaveDashboardDTO{}
dto := &m.SaveDashboardDTO{}
t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) {
t.Cleanup(func() {
fakeStore.provisionedData = nil
})
fakeStore.provisionedData = &models.DashboardProvisioning{}
origValidateAlerts := validateAlerts
t.Cleanup(func() {
validateAlerts = origValidateAlerts
})
validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
return nil
}
origUpdateAlerting := UpdateAlerting
t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
})
UpdateAlerting = func(ctx context.Context, store dashboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
}
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once()
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{}, nil).Once()
dto.Dashboard = models.NewDashboard("Dash")
dto.Dashboard.SetId(3)
@ -236,14 +175,15 @@ func TestDashboardService(t *testing.T) {
t.Run("Given provisioned dashboard", func(t *testing.T) {
t.Run("DeleteProvisionedDashboard should delete it", func(t *testing.T) {
result := setupDeleteHandlers(t, &fakeStore, true)
result := setupDeleteHandlers(t)
err := service.DeleteProvisionedDashboard(context.Background(), 1, 1)
require.NoError(t, err)
require.True(t, result.deleteWasCalled)
})
t.Run("DeleteDashboard should fail to delete it", func(t *testing.T) {
result := setupDeleteHandlers(t, &fakeStore, true)
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{}, nil).Once()
result := setupDeleteHandlers(t)
err := service.DeleteDashboard(context.Background(), 1, 1)
require.Equal(t, err, models.ErrDashboardCannotDeleteProvisionedDashboard)
require.False(t, result.deleteWasCalled)
@ -251,7 +191,7 @@ func TestDashboardService(t *testing.T) {
})
t.Run("Given non provisioned dashboard", func(t *testing.T) {
result := setupDeleteHandlers(t, &fakeStore, false)
result := setupDeleteHandlers(t)
t.Run("DeleteProvisionedDashboard should delete it", func(t *testing.T) {
err := service.DeleteProvisionedDashboard(context.Background(), 1, 1)
@ -260,6 +200,7 @@ func TestDashboardService(t *testing.T) {
})
t.Run("DeleteDashboard should delete it", func(t *testing.T) {
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(nil, nil).Once()
err := service.DeleteDashboard(context.Background(), 1, 1)
require.NoError(t, err)
require.True(t, result.deleteWasCalled)
@ -272,16 +213,9 @@ type Result struct {
deleteWasCalled bool
}
func setupDeleteHandlers(t *testing.T, fakeStore *fakeDashboardStore, provisioned bool) *Result {
func setupDeleteHandlers(t *testing.T) *Result {
t.Helper()
t.Cleanup(func() {
fakeStore.provisionedData = nil
})
if provisioned {
fakeStore.provisionedData = &models.DashboardProvisioning{}
}
result := &Result{}
bus.AddHandler("test", func(ctx context.Context, cmd *models.DeleteDashboardCommand) error {
require.Equal(t, cmd.Id, int64(1))
@ -292,32 +226,3 @@ func setupDeleteHandlers(t *testing.T, fakeStore *fakeDashboardStore, provisione
return result
}
type fakeDashboardStore struct {
dashboards.Store
validationError error
provisionedData *models.DashboardProvisioning
}
func (s *fakeDashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (
bool, error) {
return false, s.validationError
}
func (s *fakeDashboardStore) GetProvisionedDataByDashboardID(int64) (*models.DashboardProvisioning, error) {
return s.provisionedData, nil
}
func (s *fakeDashboardStore) SaveProvisionedDashboard(models.SaveDashboardCommand,
*models.DashboardProvisioning) (*models.Dashboard, error) {
return nil, nil
}
func (s *fakeDashboardStore) SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) {
return cmd.GetDashboardModel(), nil
}
func (s *fakeDashboardStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error {
return nil
}

View File

@ -1,4 +1,4 @@
package dashboards
package service
import (
"context"
@ -6,46 +6,42 @@ import (
"strings"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
)
// FolderService is a service for operating on folders.
type FolderService interface {
GetFolders(ctx context.Context, limit int64, page int64) ([]*models.Folder, error)
GetFolderByID(ctx context.Context, id int64) (*models.Folder, error)
GetFolderByUID(ctx context.Context, uid string) (*models.Folder, error)
GetFolderByTitle(ctx context.Context, title string) (*models.Folder, error)
CreateFolder(ctx context.Context, title, uid string) (*models.Folder, error)
UpdateFolder(ctx context.Context, uid string, cmd *models.UpdateFolderCommand) error
DeleteFolder(ctx context.Context, uid string, forceDeleteRules bool) (*models.Folder, error)
MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
type FolderServiceImpl struct {
dashboardService dashboards.DashboardService
dashboardStore dashboards.Store
searchService *search.SearchService
log log.Logger
}
// NewFolderService is a factory for creating a new folder service.
var NewFolderService = func(orgID int64, user *models.SignedInUser, store dashboards.Store) FolderService {
return &dashboardServiceImpl{
orgId: orgID,
user: user,
dashboardStore: store,
func ProvideFolderService(dashboardService dashboards.DashboardService, dashboardStore dashboards.Store, searchService *search.SearchService) *FolderServiceImpl {
return &FolderServiceImpl{
dashboardService: dashboardService,
dashboardStore: dashboardStore,
searchService: searchService,
log: log.New("folder-service"),
}
}
func (dr *dashboardServiceImpl) GetFolders(ctx context.Context, limit int64, page int64) ([]*models.Folder, error) {
func (f *FolderServiceImpl) GetFolders(ctx context.Context, user *models.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
searchQuery := search.Query{
SignedInUser: dr.user,
SignedInUser: user,
DashboardIds: make([]int64, 0),
FolderIds: make([]int64, 0),
Limit: limit,
OrgId: dr.orgId,
OrgId: orgID,
Type: "dash-folder",
Permission: models.PERMISSION_VIEW,
Page: page,
}
if err := bus.Dispatch(ctx, &searchQuery); err != nil {
if err := f.searchService.SearchHandler(ctx, &searchQuery); err != nil {
return nil, err
}
@ -62,17 +58,17 @@ func (dr *dashboardServiceImpl) GetFolders(ctx context.Context, limit int64, pag
return folders, nil
}
func (dr *dashboardServiceImpl) GetFolderByID(ctx context.Context, id int64) (*models.Folder, error) {
func (f *FolderServiceImpl) GetFolderByID(ctx context.Context, user *models.SignedInUser, id int64, orgID int64) (*models.Folder, error) {
if id == 0 {
return &models.Folder{Id: id, Title: "General"}, nil
}
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: id}
query := models.GetDashboardQuery{OrgId: orgID, Id: id}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(ctx, dashFolder.Id, dr.orgId, dr.user)
g := guardian.New(ctx, dashFolder.Id, orgID, user)
if canView, err := g.CanView(); err != nil || !canView {
if err != nil {
return nil, toFolderError(err)
@ -83,15 +79,15 @@ func (dr *dashboardServiceImpl) GetFolderByID(ctx context.Context, id int64) (*m
return dashToFolder(dashFolder), nil
}
func (dr *dashboardServiceImpl) GetFolderByUID(ctx context.Context, uid string) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
func (f *FolderServiceImpl) GetFolderByUID(ctx context.Context, user *models.SignedInUser, orgID int64, uid string) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: orgID, Uid: uid}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(ctx, dashFolder.Id, dr.orgId, dr.user)
g := guardian.New(ctx, dashFolder.Id, orgID, user)
if canView, err := g.CanView(); err != nil || !canView {
if err != nil {
return nil, toFolderError(err)
@ -102,13 +98,13 @@ func (dr *dashboardServiceImpl) GetFolderByUID(ctx context.Context, uid string)
return dashToFolder(dashFolder), nil
}
func (dr *dashboardServiceImpl) GetFolderByTitle(ctx context.Context, title string) (*models.Folder, error) {
dashFolder, err := dr.dashboardStore.GetFolderByTitle(dr.orgId, title)
func (f *FolderServiceImpl) GetFolderByTitle(ctx context.Context, user *models.SignedInUser, orgID int64, title string) (*models.Folder, error) {
dashFolder, err := f.dashboardStore.GetFolderByTitle(orgID, title)
if err != nil {
return nil, toFolderError(err)
}
g := guardian.New(ctx, dashFolder.Id, dr.orgId, dr.user)
g := guardian.New(ctx, dashFolder.Id, orgID, user)
if canView, err := g.CanView(); err != nil || !canView {
if err != nil {
return nil, toFolderError(err)
@ -119,11 +115,11 @@ func (dr *dashboardServiceImpl) GetFolderByTitle(ctx context.Context, title stri
return dashToFolder(dashFolder), nil
}
func (dr *dashboardServiceImpl) CreateFolder(ctx context.Context, title, uid string) (*models.Folder, error) {
func (f *FolderServiceImpl) CreateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, title, uid string) (*models.Folder, error) {
dashFolder := models.NewDashboardFolder(title)
dashFolder.OrgId = dr.orgId
dashFolder.OrgId = orgID
dashFolder.SetUid(strings.TrimSpace(uid))
userID := dr.user.UserId
userID := user.UserId
if userID == 0 {
userID = -1
}
@ -131,23 +127,23 @@ func (dr *dashboardServiceImpl) CreateFolder(ctx context.Context, title, uid str
dashFolder.UpdatedBy = userID
dashFolder.UpdateSlug()
dto := &SaveDashboardDTO{
dto := &dashboards.SaveDashboardDTO{
Dashboard: dashFolder,
OrgId: dr.orgId,
User: dr.user,
OrgId: orgID,
User: user,
}
saveDashboardCmd, err := dr.buildSaveDashboardCommand(ctx, dto, false, false)
saveDashboardCmd, err := f.dashboardService.BuildSaveDashboardCommand(ctx, dto, false, false)
if err != nil {
return nil, toFolderError(err)
}
dash, err := dr.dashboardStore.SaveDashboard(*saveDashboardCmd)
dash, err := f.dashboardStore.SaveDashboard(*saveDashboardCmd)
if err != nil {
return nil, toFolderError(err)
}
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: dash.Id}
query := models.GetDashboardQuery{OrgId: orgID, Id: dash.Id}
dashFolder, err = getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
@ -156,33 +152,33 @@ func (dr *dashboardServiceImpl) CreateFolder(ctx context.Context, title, uid str
return dashToFolder(dashFolder), nil
}
func (dr *dashboardServiceImpl) UpdateFolder(ctx context.Context, existingUid string, cmd *models.UpdateFolderCommand) error {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: existingUid}
func (f *FolderServiceImpl) UpdateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
query := models.GetDashboardQuery{OrgId: orgID, Uid: existingUid}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return toFolderError(err)
}
cmd.UpdateDashboardModel(dashFolder, dr.orgId, dr.user.UserId)
cmd.UpdateDashboardModel(dashFolder, orgID, user.UserId)
dto := &SaveDashboardDTO{
dto := &dashboards.SaveDashboardDTO{
Dashboard: dashFolder,
OrgId: dr.orgId,
User: dr.user,
OrgId: orgID,
User: user,
Overwrite: cmd.Overwrite,
}
saveDashboardCmd, err := dr.buildSaveDashboardCommand(ctx, dto, false, false)
saveDashboardCmd, err := f.dashboardService.BuildSaveDashboardCommand(ctx, dto, false, false)
if err != nil {
return toFolderError(err)
}
dash, err := dr.dashboardStore.SaveDashboard(*saveDashboardCmd)
dash, err := f.dashboardStore.SaveDashboard(*saveDashboardCmd)
if err != nil {
return toFolderError(err)
}
query = models.GetDashboardQuery{OrgId: dr.orgId, Id: dash.Id}
query = models.GetDashboardQuery{OrgId: orgID, Id: dash.Id}
dashFolder, err = getFolder(ctx, query)
if err != nil {
return toFolderError(err)
@ -193,14 +189,14 @@ func (dr *dashboardServiceImpl) UpdateFolder(ctx context.Context, existingUid st
return nil
}
func (dr *dashboardServiceImpl) DeleteFolder(ctx context.Context, uid string, forceDeleteRules bool) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
func (f *FolderServiceImpl) DeleteFolder(ctx context.Context, user *models.SignedInUser, orgID int64, uid string, forceDeleteRules bool) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: orgID, Uid: uid}
dashFolder, err := getFolder(ctx, query)
if err != nil {
return nil, toFolderError(err)
}
guardian := guardian.New(ctx, dashFolder.Id, dr.orgId, dr.user)
guardian := guardian.New(ctx, dashFolder.Id, orgID, user)
if canSave, err := guardian.CanSave(); err != nil || !canSave {
if err != nil {
return nil, toFolderError(err)
@ -208,7 +204,7 @@ func (dr *dashboardServiceImpl) DeleteFolder(ctx context.Context, uid string, fo
return nil, models.ErrFolderAccessDenied
}
deleteCmd := models.DeleteDashboardCommand{OrgId: dr.orgId, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules}
deleteCmd := models.DeleteDashboardCommand{OrgId: orgID, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules}
if err := bus.Dispatch(ctx, &deleteCmd); err != nil {
return nil, toFolderError(err)
}
@ -216,6 +212,10 @@ func (dr *dashboardServiceImpl) DeleteFolder(ctx context.Context, uid string, fo
return dashToFolder(dashFolder), nil
}
func (f *FolderServiceImpl) MakeUserAdmin(ctx context.Context, orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error {
return f.dashboardService.MakeUserAdmin(ctx, orgID, userID, folderID, setViewAndEditPermissions)
}
func getFolder(ctx context.Context, query models.GetDashboardQuery) (*models.Dashboard, error) {
if err := bus.Dispatch(ctx, &query); err != nil {
return nil, toFolderError(err)

View File

@ -1,25 +1,34 @@
package dashboards
//go:build integration
// +build integration
package service
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
var orgID = int64(1)
var user = &models.SignedInUser{UserId: 1}
func TestFolderService(t *testing.T) {
t.Run("Folder service tests", func(t *testing.T) {
service := dashboardServiceImpl{
orgId: 1,
user: &models.SignedInUser{UserId: 1},
dashboardStore: &fakeDashboardStore{},
}
store := &database.FakeDashboardStore{}
defer store.AssertExpectations(t)
service := ProvideFolderService(
&dashboards.FakeDashboardService{DashboardService: ProvideDashboardService(store)},
store,
nil,
)
t.Run("Given user has no permissions", func(t *testing.T) {
origNewGuardian := guardian.New
@ -30,37 +39,30 @@ func TestFolderService(t *testing.T) {
return nil
})
origStore := service.dashboardStore
t.Cleanup(func() {
service.dashboardStore = origStore
})
service.dashboardStore = &fakeDashboardStore{
validationError: models.ErrDashboardUpdateAccessDenied,
}
t.Run("When get folder by id should return access denied error", func(t *testing.T) {
_, err := service.GetFolderByID(context.Background(), 1)
_, err := service.GetFolderByID(context.Background(), user, 1, orgID)
require.Equal(t, err, models.ErrFolderAccessDenied)
})
t.Run("When get folder by id, with id = 0 should return default folder", func(t *testing.T) {
folder, err := service.GetFolderByID(context.Background(), 0)
folder, err := service.GetFolderByID(context.Background(), user, 0, orgID)
require.NoError(t, err)
require.Equal(t, folder, &models.Folder{Id: 0, Title: "General"})
})
t.Run("When get folder by uid should return access denied error", func(t *testing.T) {
_, err := service.GetFolderByUID(context.Background(), "uid")
_, err := service.GetFolderByUID(context.Background(), user, orgID, "uid")
require.Equal(t, err, models.ErrFolderAccessDenied)
})
t.Run("When creating folder should return access denied error", func(t *testing.T) {
_, err := service.CreateFolder(context.Background(), "Folder", "")
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Times(2)
_, err := service.CreateFolder(context.Background(), user, orgID, "Folder", "")
require.Equal(t, err, models.ErrFolderAccessDenied)
})
t.Run("When updating folder should return access denied error", func(t *testing.T) {
err := service.UpdateFolder(context.Background(), "uid", &models.UpdateFolderCommand{
err := service.UpdateFolder(context.Background(), user, orgID, "uid", &models.UpdateFolderCommand{
Uid: "uid",
Title: "Folder",
})
@ -68,7 +70,7 @@ func TestFolderService(t *testing.T) {
})
t.Run("When deleting folder by uid should return access denied error", func(t *testing.T) {
_, err := service.DeleteFolder(context.Background(), "uid", false)
_, err := service.DeleteFolder(context.Background(), user, orgID, "uid", false)
require.Error(t, err)
require.Equal(t, err, models.ErrFolderAccessDenied)
})
@ -90,15 +92,6 @@ func TestFolderService(t *testing.T) {
return nil
})
origUpdateAlerting := UpdateAlerting
t.Cleanup(func() {
UpdateAlerting = origUpdateAlerting
})
UpdateAlerting = func(ctx context.Context, store dashboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
}
bus.AddHandler("test", func(ctx context.Context, cmd *models.SaveDashboardCommand) error {
cmd.Result = dash
return nil
@ -109,12 +102,15 @@ func TestFolderService(t *testing.T) {
})
t.Run("When creating folder should not return access denied error", func(t *testing.T) {
_, err := service.CreateFolder(context.Background(), "Folder", "")
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Times(2)
store.On("SaveDashboard", mock.Anything).Return(&models.Dashboard{Id: 1}, nil).Once()
_, err := service.CreateFolder(context.Background(), user, orgID, "Folder", "")
require.NoError(t, err)
})
t.Run("When updating folder should not return access denied error", func(t *testing.T) {
err := service.UpdateFolder(context.Background(), "uid", &models.UpdateFolderCommand{
store.On("SaveDashboard", mock.Anything).Return(&models.Dashboard{Id: 1}, nil).Once()
err := service.UpdateFolder(context.Background(), user, orgID, "uid", &models.UpdateFolderCommand{
Uid: "uid",
Title: "Folder",
})
@ -122,7 +118,7 @@ func TestFolderService(t *testing.T) {
})
t.Run("When deleting folder by uid should not return access denied error", func(t *testing.T) {
_, err := service.DeleteFolder(context.Background(), "uid", false)
_, err := service.DeleteFolder(context.Background(), user, orgID, "uid", false)
require.NoError(t, err)
})
@ -145,14 +141,14 @@ func TestFolderService(t *testing.T) {
})
t.Run("When get folder by id should return folder", func(t *testing.T) {
f, _ := service.GetFolderByID(context.Background(), 1)
f, _ := service.GetFolderByID(context.Background(), user, orgID, 1)
require.Equal(t, f.Id, dashFolder.Id)
require.Equal(t, f.Uid, dashFolder.Uid)
require.Equal(t, f.Title, dashFolder.Title)
})
t.Run("When get folder by uid should return folder", func(t *testing.T) {
f, _ := service.GetFolderByUID(context.Background(), "uid")
f, _ := service.GetFolderByUID(context.Background(), user, orgID, "uid")
require.Equal(t, f.Id, dashFolder.Id)
require.Equal(t, f.Uid, dashFolder.Uid)
require.Equal(t, f.Title, dashFolder.Title)

View File

@ -0,0 +1,16 @@
package dashboards
import (
"time"
"github.com/grafana/grafana/pkg/models"
)
type SaveDashboardDTO struct {
OrgId int64
UpdatedAt time.Time
User *models.SignedInUser
Message string
Overwrite bool
Dashboard *models.Dashboard
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
)
@ -32,9 +31,7 @@ func (l *LibraryElementService) requirePermissionsOnFolder(ctx context.Context,
if isGeneralFolder(folderID) && user.HasRole(models.ROLE_VIEWER) {
return models.ErrFolderAccessDenied
}
s := dashboards.NewFolderService(user.OrgId, user, l.SQLStore)
folder, err := s.GetFolderByID(ctx, folderID)
folder, err := l.folderService.GetFolderByID(ctx, user, folderID, user.OrgId)
if err != nil {
return err
}

View File

@ -6,15 +6,17 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, routeRegister routing.RouteRegister) *LibraryElementService {
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, routeRegister routing.RouteRegister, folderService dashboards.FolderService) *LibraryElementService {
l := &LibraryElementService{
Cfg: cfg,
SQLStore: sqlStore,
RouteRegister: routeRegister,
folderService: folderService,
log: log.New("library-elements"),
}
l.registerAPIEndpoints()
@ -36,6 +38,7 @@ type LibraryElementService struct {
Cfg *setting.Cfg
SQLStore *sqlstore.SQLStore
RouteRegister routing.RouteRegister
folderService dashboards.FolderService
log log.Logger
}

View File

@ -9,14 +9,13 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
@ -194,16 +193,9 @@ func createDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, user models.Sign
User: &user,
Overwrite: false,
}
origUpdateAlerting := dashboards.UpdateAlerting
t.Cleanup(func() {
dashboards.UpdateAlerting = origUpdateAlerting
})
dashboards.UpdateAlerting = func(ctx context.Context, store dboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
}
dashboard, err := dashboards.NewService(sqlStore).SaveDashboard(context.Background(), dashItem, true)
dashboardStore := database.ProvideDashboardStore(sqlStore)
dashboard, err := dashboardservice.ProvideDashboardService(dashboardStore).SaveDashboard(context.Background(), dashItem, true)
require.NoError(t, err)
return dashboard
@ -213,17 +205,19 @@ func createFolderWithACL(t *testing.T, sqlStore *sqlstore.SQLStore, title string
items []folderACLItem) *models.Folder {
t.Helper()
s := dashboards.NewFolderService(user.OrgId, &user, sqlStore)
dashboardStore := database.ProvideDashboardStore(sqlStore)
d := dashboardservice.ProvideDashboardService(dashboardStore)
s := dashboardservice.ProvideFolderService(d, dashboardStore, nil)
t.Logf("Creating folder with title and UID %q", title)
folder, err := s.CreateFolder(context.Background(), title, title)
folder, err := s.CreateFolder(context.Background(), &user, user.OrgId, title, title)
require.NoError(t, err)
updateFolderACL(t, sqlStore, folder.Id, items)
updateFolderACL(t, dashboardStore, folder.Id, items)
return folder
}
func updateFolderACL(t *testing.T, sqlStore *sqlstore.SQLStore, folderID int64, items []folderACLItem) {
func updateFolderACL(t *testing.T, dashboardStore *database.DashboardStore, folderID int64, items []folderACLItem) {
t.Helper()
if len(items) == 0 {
@ -243,7 +237,7 @@ func updateFolderACL(t *testing.T, sqlStore *sqlstore.SQLStore, folderID int64,
})
}
err := sqlStore.UpdateDashboardACL(context.Background(), folderID, aclItems)
err := dashboardStore.UpdateDashboardACL(context.Background(), folderID, aclItems)
require.NoError(t, err)
}
@ -297,9 +291,12 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
orgID := int64(1)
role := models.ROLE_ADMIN
sqlStore := sqlstore.InitTestDB(t)
dashboardStore := database.ProvideDashboardStore(sqlStore)
dashboardService := dashboardservice.ProvideDashboardService(dashboardStore)
service := LibraryElementService{
Cfg: setting.NewCfg(),
SQLStore: sqlStore,
Cfg: setting.NewCfg(),
SQLStore: sqlStore,
folderService: dashboardservice.ProvideFolderService(dashboardService, dashboardStore, nil),
}
user := models.SignedInUser{

View File

@ -8,15 +8,16 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
const userInDbName = "user_in_db"
@ -1413,16 +1414,9 @@ func createDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, user *models.Sig
User: user,
Overwrite: false,
}
origUpdateAlerting := dashboards.UpdateAlerting
t.Cleanup(func() {
dashboards.UpdateAlerting = origUpdateAlerting
})
dashboards.UpdateAlerting = func(ctx context.Context, store dboards.Store, orgID int64, dashboard *models.Dashboard,
user *models.SignedInUser) error {
return nil
}
dashboard, err := dashboards.NewService(sqlStore).SaveDashboard(context.Background(), dashItem, true)
dashboadStore := database.ProvideDashboardStore(sqlStore)
dashboard, err := dashboardservice.ProvideDashboardService(dashboadStore).SaveDashboard(context.Background(), dashItem, true)
require.NoError(t, err)
return dashboard
@ -1432,17 +1426,19 @@ func createFolderWithACL(t *testing.T, sqlStore *sqlstore.SQLStore, title string
items []folderACLItem) *models.Folder {
t.Helper()
s := dashboards.NewFolderService(user.OrgId, user, sqlStore)
dashboardStore := database.ProvideDashboardStore(sqlStore)
d := dashboardservice.ProvideDashboardService(dashboardStore)
s := dashboardservice.ProvideFolderService(d, dashboardStore, nil)
t.Logf("Creating folder with title and UID %q", title)
folder, err := s.CreateFolder(context.Background(), title, title)
folder, err := s.CreateFolder(context.Background(), user, user.OrgId, title, title)
require.NoError(t, err)
updateFolderACL(t, sqlStore, folder.Id, items)
updateFolderACL(t, dashboardStore, folder.Id, items)
return folder
}
func updateFolderACL(t *testing.T, sqlStore *sqlstore.SQLStore, folderID int64, items []folderACLItem) {
func updateFolderACL(t *testing.T, dashboardStore *database.DashboardStore, folderID int64, items []folderACLItem) {
t.Helper()
if len(items) == 0 {
@ -1462,7 +1458,7 @@ func updateFolderACL(t *testing.T, sqlStore *sqlstore.SQLStore, folderID int64,
})
}
err := sqlStore.UpdateDashboardACL(context.Background(), folderID, aclItems)
err := dashboardStore.UpdateDashboardACL(context.Background(), folderID, aclItems)
require.NoError(t, err)
}
@ -1519,14 +1515,14 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
orgID := int64(1)
role := models.ROLE_ADMIN
sqlStore := sqlstore.InitTestDB(t)
elementService := libraryelements.LibraryElementService{
Cfg: cfg,
SQLStore: sqlStore,
}
dashboardStore := database.ProvideDashboardStore(sqlStore)
folderService := dashboardservice.ProvideFolderService(dashboardservice.ProvideDashboardService(dashboardStore), dashboardStore, nil)
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService)
service := LibraryPanelService{
Cfg: cfg,
SQLStore: sqlStore,
LibraryElementService: &elementService,
LibraryElementService: elementService,
}
user := &models.SignedInUser{
@ -1555,7 +1551,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
user: user,
ctx: context.Background(),
service: &service,
elementService: &elementService,
elementService: elementService,
sqlStore: sqlStore,
}

View File

@ -5,12 +5,11 @@ import (
"net/url"
"github.com/benbjohnson/clock"
"golang.org/x/sync/errgroup"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/ngalert/api"
@ -25,11 +24,12 @@ import (
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"golang.org/x/sync/errgroup"
)
func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, routeRegister routing.RouteRegister,
sqlStore *sqlstore.SQLStore, kvStore kvstore.KVStore, expressionService *expr.Service, dataProxy *datasourceproxy.DataSourceProxyService,
quotaService *quota.QuotaService, secretsService secrets.Service, notificationService notifications.Service, m *metrics.NGAlert) (*AlertNG, error) {
quotaService *quota.QuotaService, secretsService secrets.Service, notificationService notifications.Service, m *metrics.NGAlert, folderService dashboards.FolderService) (*AlertNG, error) {
ng := &AlertNG{
Cfg: cfg,
DataSourceCache: dataSourceCache,
@ -41,8 +41,9 @@ func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService,
QuotaService: quotaService,
SecretsService: secretsService,
Metrics: m,
NotificationService: notificationService,
Log: log.New("ngalert"),
NotificationService: notificationService,
folderService: folderService,
}
if ng.IsDisabled() {
@ -72,6 +73,7 @@ type AlertNG struct {
Log log.Logger
schedule schedule.ScheduleService
stateManager *state.Manager
folderService dashboards.FolderService
// Alerting notification services
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
@ -85,6 +87,7 @@ func (ng *AlertNG) init() error {
DefaultInterval: ng.Cfg.UnifiedAlerting.DefaultAlertForDuration,
SQLStore: ng.SQLStore,
Logger: ng.Log,
FolderService: ng.folderService,
}
decryptFn := ng.SecretsService.GetDecryptedValue

View File

@ -12,8 +12,6 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
@ -395,12 +393,11 @@ func (st DBstore) GetRuleGroupAlertRules(ctx context.Context, query *ngmodels.Li
// GetNamespaces returns the folders that are visible to the user
func (st DBstore) GetNamespaces(ctx context.Context, orgID int64, user *models.SignedInUser) (map[string]*models.Folder, error) {
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
namespaceMap := make(map[string]*models.Folder)
var page int64 = 1
for {
// if limit is negative; it fetches at most 1000
folders, err := s.GetFolders(ctx, -1, page)
folders, err := st.FolderService.GetFolders(ctx, user, orgID, -1, page)
if err != nil {
return nil, err
}
@ -419,8 +416,7 @@ func (st DBstore) GetNamespaces(ctx context.Context, orgID int64, user *models.S
// GetNamespaceByTitle is a handler for retrieving a namespace by its title. Alerting rules follow a Grafana folder-like structure which we call namespaces.
func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, orgID int64, user *models.SignedInUser, withCanSave bool) (*models.Folder, error) {
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
folder, err := s.GetFolderByTitle(ctx, namespace)
folder, err := st.FolderService.GetFolderByTitle(ctx, user, orgID, namespace)
if err != nil {
return nil, err
}

View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
@ -31,4 +32,5 @@ type DBstore struct {
DefaultInterval time.Duration
SQLStore *sqlstore.SQLStore
Logger log.Logger
FolderService dashboards.FolderService
}

View File

@ -9,6 +9,8 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
databasestore "github.com/grafana/grafana/pkg/services/dashboards/database"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/services/ngalert"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
@ -40,9 +42,11 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, *
m := metrics.NewNGAlert(prometheus.NewRegistry())
sqlStore := sqlstore.InitTestDB(t)
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
dashboardStore := databasestore.ProvideDashboardStore(sqlStore)
folderService := dashboardservice.ProvideFolderService(dashboardservice.ProvideDashboardService(dashboardStore), dashboardStore, nil)
ng, err := ngalert.ProvideService(
cfg, nil, routing.NewRouteRegister(), sqlStore,
nil, nil, nil, nil, secretsService, nil, m,
nil, nil, nil, nil, secretsService, nil, m, folderService,
)
require.NoError(t, err)
return ng, &store.DBstore{

View File

@ -6,9 +6,9 @@ import (
"os"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/util/errutil"
)

View File

@ -13,10 +13,10 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
"github.com/grafana/grafana/pkg/util"
)
@ -41,7 +41,7 @@ type FileReader struct {
}
// NewDashboardFileReader returns a new filereader based on `config`
func NewDashboardFileReader(cfg *config, log log.Logger, store dboards.Store) (*FileReader, error) {
func NewDashboardFileReader(cfg *config, log log.Logger, store dashboards.Store) (*FileReader, error) {
var path string
path, ok := cfg.Options["path"].(string)
if !ok {
@ -62,7 +62,7 @@ func NewDashboardFileReader(cfg *config, log log.Logger, store dboards.Store) (*
Cfg: cfg,
Path: path,
log: log,
dashboardProvisioningService: dashboards.NewProvisioningService(store),
dashboardProvisioningService: dashboardservice.ProvideDashboardService(store),
FoldersFromFilesStructure: foldersFromFilesStructure,
usageTracker: newUsageTracker(),
}, nil

View File

@ -2,8 +2,6 @@ package dashboards
import (
"context"
"fmt"
"math/rand"
"os"
"path/filepath"
"runtime"
@ -11,13 +9,12 @@ import (
"time"
"github.com/grafana/grafana/pkg/bus"
dboards "github.com/grafana/grafana/pkg/dashboards"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
@ -28,10 +25,9 @@ const (
containingID = "testdata/test-dashboards/containing-id"
unprovision = "testdata/test-dashboards/unprovision"
foldersFromFilesStructure = "testdata/test-dashboards/folders-from-files-structure"
configName = "default"
)
var fakeService *fakeDashboardProvisioningService
func TestCreatingNewDashboardFileReader(t *testing.T) {
setup := func() *config {
return &config{
@ -98,17 +94,14 @@ func TestDashboardFileReader(t *testing.T) {
logger := log.New("test.logger")
cfg := &config{}
origNewDashboardProvisioningService := dashboards.NewProvisioningService
defer func() {
dashboards.NewProvisioningService = origNewDashboardProvisioningService
}()
fakeService := &dashboards.FakeDashboardProvisioning{}
defer fakeService.AssertExpectations(t)
setup := func() {
bus.ClearBusHandlers()
fakeService = mockDashboardProvisioningService()
bus.AddHandler("test", mockGetDashboardQuery)
cfg = &config{
Name: "Default",
Name: configName,
Type: "file",
OrgID: 1,
Folder: "",
@ -122,45 +115,38 @@ func TestDashboardFileReader(t *testing.T) {
cfg.Options["path"] = defaultDashboards
cfg.Folder = "Team A"
fakeService.On("GetProvisionedDashboardData", configName).Return(nil, nil).Once()
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{Id: 1}, nil).Once()
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{Id: 2}, nil).Times(2)
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
folders := 0
dashboards := 0
for _, i := range fakeService.inserted {
if i.Dashboard.IsFolder {
folders++
} else {
dashboards++
}
}
require.Equal(t, folders, 1)
require.Equal(t, dashboards, 2)
})
t.Run("Can read default dashboard and replace old version in database", func(t *testing.T) {
setup()
cfg.Options["path"] = oneDashboard
stat, _ := os.Stat(oneDashboard + "/dashboard1.json")
fakeService.getDashboard = append(fakeService.getDashboard, &models.Dashboard{
Updated: stat.ModTime().AddDate(0, 0, -1),
Slug: "grafana",
})
inserted := 0
fakeService.On("GetProvisionedDashboardData", configName).Return(nil, nil).Once()
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).
Return(&models.Dashboard{}, nil).Once().
Run(func(args mock.Arguments) {
inserted++
})
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 1)
assert.Equal(t, inserted, 1)
})
t.Run("Dashboard with older timestamp and the same checksum will not replace imported dashboard", func(t *testing.T) {
@ -179,23 +165,23 @@ func TestDashboardFileReader(t *testing.T) {
checksum, err := util.Md5Sum(file)
require.NoError(t, err)
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
CheckSum: checksum,
},
provisionedDashboard := []*models.DashboardProvisioning{
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
CheckSum: checksum,
},
}
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 0)
})
t.Run("Dashboard with older timestamp and different checksum will replace imported dashboard", func(t *testing.T) {
@ -206,23 +192,24 @@ func TestDashboardFileReader(t *testing.T) {
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
CheckSum: "fakechecksum",
},
provisionedDashboard := []*models.DashboardProvisioning{
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, +1).Unix(),
CheckSum: "fakechecksum",
},
}
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 1)
})
t.Run("Dashboard with newer timestamp and the same checksum will not replace imported dashboard", func(t *testing.T) {
@ -241,23 +228,23 @@ func TestDashboardFileReader(t *testing.T) {
checksum, err := util.Md5Sum(file)
require.NoError(t, err)
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
CheckSum: checksum,
},
provisionedDashboard := []*models.DashboardProvisioning{
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
CheckSum: checksum,
},
}
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 0)
})
t.Run("Dashboard with newer timestamp and different checksum should replace imported dashboard", func(t *testing.T) {
@ -268,36 +255,39 @@ func TestDashboardFileReader(t *testing.T) {
stat, err := os.Stat(oneDashboard + "/dashboard1.json")
require.NoError(t, err)
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": {
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
CheckSum: "fakechecksum",
},
provisionedDashboard := []*models.DashboardProvisioning{
{
Name: "Default",
ExternalId: absPath,
Updated: stat.ModTime().AddDate(0, 0, -1).Unix(),
CheckSum: "fakechecksum",
},
}
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 1)
})
t.Run("Overrides id from dashboard.json files", func(t *testing.T) {
setup()
cfg.Options["path"] = containingID
fakeService.On("GetProvisionedDashboardData", configName).Return(nil, nil).Once()
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 1)
})
t.Run("Get folder from files structure", func(t *testing.T) {
@ -305,40 +295,16 @@ func TestDashboardFileReader(t *testing.T) {
cfg.Options["path"] = foldersFromFilesStructure
cfg.Options["foldersFromFilesStructure"] = true
fakeService.On("GetProvisionedDashboardData", configName).Return(nil, nil).Once()
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(3)
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.inserted), 5)
foldersCount := 0
for _, d := range fakeService.inserted {
if d.Dashboard.IsFolder {
foldersCount++
}
}
require.Equal(t, foldersCount, 2)
foldersAndDashboards := make(map[string]struct{}, 5)
for _, d := range fakeService.inserted {
title := d.Dashboard.Title
if _, ok := foldersAndDashboards[title]; ok {
require.Nil(t, fmt.Errorf("dashboard title %q already exists", title))
}
switch title {
case "folderOne", "folderTwo":
require.True(t, d.Dashboard.IsFolder)
case "Grafana1", "Grafana2", "RootDashboard":
require.False(t, d.Dashboard.IsFolder)
default:
require.Nil(t, fmt.Errorf("unknown dashboard title %q", title))
}
foldersAndDashboards[title] = struct{}{}
}
})
t.Run("Invalid configuration should return error", func(t *testing.T) {
@ -367,30 +333,23 @@ func TestDashboardFileReader(t *testing.T) {
cfg1 := &config{Name: "1", Type: "file", OrgID: 1, Folder: "f1", Options: map[string]interface{}{"path": containingID}}
cfg2 := &config{Name: "2", Type: "file", OrgID: 1, Folder: "f2", Options: map[string]interface{}{"path": containingID}}
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return(nil, nil).Times(2)
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
reader1, err := NewDashboardFileReader(cfg1, logger, nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader1.walkDisk(context.Background())
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader2.walkDisk(context.Background())
require.NoError(t, err)
var folderCount int
var dashCount int
for _, o := range fakeService.inserted {
if o.Dashboard.IsFolder {
folderCount++
} else {
dashCount++
}
}
require.Equal(t, folderCount, 2)
require.Equal(t, dashCount, 2)
})
})
@ -422,16 +381,9 @@ func TestDashboardFileReader(t *testing.T) {
},
}
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
_, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
require.NoError(t, err)
inserted := false
for _, d := range fakeService.inserted {
if d.Dashboard.IsFolder && d.Dashboard.Id == folderID {
inserted = true
}
}
require.Equal(t, len(fakeService.inserted), 1)
require.True(t, inserted)
})
t.Run("Walking the folder with dashboards", func(t *testing.T) {
@ -456,56 +408,53 @@ func TestDashboardFileReader(t *testing.T) {
absPath2, err := filepath.Abs(unprovision + "/dashboard2.json")
require.NoError(t, err)
provisionedDashboard := []*models.DashboardProvisioning{
{DashboardId: 1, Name: "Default", ExternalId: absPath1},
{DashboardId: 2, Name: "Default", ExternalId: absPath2},
}
setupFakeService := func() {
setup()
cfg = &config{
Name: "Default",
Name: configName,
Type: "file",
OrgID: 1,
Options: map[string]interface{}{
"folder": unprovision,
},
}
fakeService.inserted = []*dashboards.SaveDashboardDTO{
{Dashboard: &models.Dashboard{Id: 1}},
{Dashboard: &models.Dashboard{Id: 2}},
}
fakeService.provisioned = map[string][]*models.DashboardProvisioning{
"Default": {
{DashboardId: 1, Name: "Default", ExternalId: absPath1},
{DashboardId: 2, Name: "Default", ExternalId: absPath2},
},
}
}
t.Run("Missing dashboard should be unprovisioned if DisableDeletion = true", func(t *testing.T) {
setupFakeService()
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
fakeService.On("UnprovisionDashboard", mock.Anything, mock.Anything).Return(nil).Once()
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
cfg.DisableDeletion = true
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.provisioned["Default"]), 1)
require.Equal(t, fakeService.provisioned["Default"][0].ExternalId, absPath1)
})
t.Run("Missing dashboard should be deleted if DisableDeletion = false", func(t *testing.T) {
setupFakeService()
fakeService.On("GetProvisionedDashboardData", configName).Return(provisionedDashboard, nil).Once()
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
fakeService.On("DeleteProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
reader, err := NewDashboardFileReader(cfg, logger, nil)
reader.dashboardProvisioningService = fakeService
require.NoError(t, err)
err = reader.walkDisk(context.Background())
require.NoError(t, err)
require.Equal(t, len(fakeService.provisioned["Default"]), 1)
require.Equal(t, fakeService.provisioned["Default"][0].ExternalId, absPath1)
require.Equal(t, len(fakeService.inserted), 1)
require.Equal(t, fakeService.inserted[0].Dashboard.Id, int64(1))
})
})
}
@ -539,111 +488,6 @@ func (ffi FakeFileInfo) Sys() interface{} {
return nil
}
func mockDashboardProvisioningService() *fakeDashboardProvisioningService {
mock := fakeDashboardProvisioningService{
provisioned: map[string][]*models.DashboardProvisioning{},
}
dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
return &mock
}
return &mock
}
type fakeDashboardProvisioningService struct {
dashboards.DashboardProvisioningService
inserted []*dashboards.SaveDashboardDTO
provisioned map[string][]*models.DashboardProvisioning
getDashboard []*models.Dashboard
}
func (s *fakeDashboardProvisioningService) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
if _, ok := s.provisioned[name]; !ok {
s.provisioned[name] = []*models.DashboardProvisioning{}
}
return s.provisioned[name], nil
}
func (s *fakeDashboardProvisioningService) SaveProvisionedDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO,
provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
// Copy the structs as we need to change them but do not want to alter outside world.
var copyProvisioning = &models.DashboardProvisioning{}
*copyProvisioning = *provisioning
var copyDto = &dashboards.SaveDashboardDTO{}
*copyDto = *dto
if copyDto.Dashboard.Id == 0 {
copyDto.Dashboard.Id = rand.Int63n(1000000)
} else {
err := s.DeleteProvisionedDashboard(context.Background(), dto.Dashboard.Id, dto.Dashboard.OrgId)
// Lets delete existing so we do not have duplicates
if err != nil {
return nil, err
}
}
s.inserted = append(s.inserted, dto)
if _, ok := s.provisioned[provisioning.Name]; !ok {
s.provisioned[provisioning.Name] = []*models.DashboardProvisioning{}
}
for _, val := range s.provisioned[provisioning.Name] {
if val.DashboardId == dto.Dashboard.Id && val.Name == provisioning.Name {
// Do not insert duplicates
return dto.Dashboard, nil
}
}
copyProvisioning.DashboardId = copyDto.Dashboard.Id
s.provisioned[provisioning.Name] = append(s.provisioned[provisioning.Name], copyProvisioning)
return dto.Dashboard, nil
}
func (s *fakeDashboardProvisioningService) SaveFolderForProvisionedDashboards(ctx context.Context, dto *dashboards.SaveDashboardDTO) (*models.Dashboard, error) {
s.inserted = append(s.inserted, dto)
return dto.Dashboard, nil
}
func (s *fakeDashboardProvisioningService) UnprovisionDashboard(ctx context.Context, dashboardID int64) error {
for key, val := range s.provisioned {
for index, dashboard := range val {
if dashboard.DashboardId == dashboardID {
s.provisioned[key] = append(s.provisioned[key][:index], s.provisioned[key][index+1:]...)
}
}
}
return nil
}
func (s *fakeDashboardProvisioningService) DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error {
err := s.UnprovisionDashboard(ctx, dashboardID)
if err != nil {
return err
}
for index, val := range s.inserted {
if val.Dashboard.Id == dashboardID {
s.inserted = append(s.inserted[:index], s.inserted[util.MinInt(index+1, len(s.inserted)):]...)
}
}
return nil
}
func (s *fakeDashboardProvisioningService) GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
return nil, nil
}
func mockGetDashboardQuery(ctx context.Context, cmd *models.GetDashboardQuery) error {
for _, d := range fakeService.getDashboard {
if d.Slug == cmd.Slug {
cmd.Result = d
return nil
}
}
func mockGetDashboardQuery(_ context.Context, _ *models.GetDashboardQuery) error {
return models.ErrDashboardNotFound
}

View File

@ -5,10 +5,12 @@ import (
"sort"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
const (
@ -18,7 +20,8 @@ const (
func TestDuplicatesValidator(t *testing.T) {
bus.ClearBusHandlers()
fakeService = mockDashboardProvisioningService()
fakeService := &dashboards.FakeDashboardProvisioning{}
defer fakeService.AssertExpectations(t)
bus.AddHandler("test", mockGetDashboardQuery)
cfg := &config{
@ -32,6 +35,11 @@ func TestDuplicatesValidator(t *testing.T) {
t.Run("Duplicates validator should collect info about duplicate UIDs and titles within folders", func(t *testing.T) {
const folderName = "duplicates-validator-folder"
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(3)
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(2)
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
require.NoError(t, err)
@ -47,9 +55,11 @@ func TestDuplicatesValidator(t *testing.T) {
}
reader1, err := NewDashboardFileReader(cfg1, logger, nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
duplicateValidator := newDuplicateValidator(logger, []*FileReader{reader1, reader2})
@ -79,6 +89,11 @@ func TestDuplicatesValidator(t *testing.T) {
t.Run("Duplicates validator should not collect info about duplicate UIDs and titles within folders for different orgs", func(t *testing.T) {
const folderName = "duplicates-validator-folder"
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(3)
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(2)
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
require.NoError(t, err)
@ -94,9 +109,11 @@ func TestDuplicatesValidator(t *testing.T) {
}
reader1, err := NewDashboardFileReader(cfg1, logger, nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
duplicateValidator := newDuplicateValidator(logger, []*FileReader{reader1, reader2})
@ -135,6 +152,10 @@ 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(&models.Dashboard{}, nil).Times(5)
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(3)
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(6)
cfg1 := &config{
Name: "first", Type: "file", OrgID: 1, Folder: "duplicates-validator-folder",
Options: map[string]interface{}{"path": twoDashboardsWithUID},
@ -149,12 +170,15 @@ func TestDuplicatesValidator(t *testing.T) {
}
reader1, err := NewDashboardFileReader(cfg1, logger, nil)
reader1.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader2, err := NewDashboardFileReader(cfg2, logger, nil)
reader2.dashboardProvisioningService = fakeService
require.NoError(t, err)
reader3, err := NewDashboardFileReader(cfg3, logger, nil)
reader3.dashboardProvisioningService = fakeService
require.NoError(t, err)
duplicateValidator := newDuplicateValidator(logger, []*FileReader{reader1, reader2, reader3})

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
plugifaces "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
@ -20,10 +21,9 @@ import (
)
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginStore plugifaces.Store,
encryptionService encryption.Internal, notificatonService *notifications.NotificationService) (*ProvisioningServiceImpl, error) {
encryptionService encryption.Internal, notificatonService *notifications.NotificationService, dashboardsStore dashboardservice.Store) (*ProvisioningServiceImpl, error) {
s := &ProvisioningServiceImpl{
Cfg: cfg,
SQLStore: sqlStore,
pluginStore: pluginStore,
EncryptionService: encryptionService,
NotificationService: notificatonService,
@ -32,6 +32,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginStore p
provisionNotifiers: notifiers.Provision,
provisionDatasources: datasources.Provision,
provisionPlugins: plugins.Provision,
dashboardsStore: dashboardsStore,
}
return s, nil
}
@ -88,6 +89,7 @@ type ProvisioningServiceImpl struct {
provisionDatasources func(context.Context, string) error
provisionPlugins func(context.Context, string, plugifaces.Store) error
mutex sync.Mutex
dashboardsStore dashboardservice.Store
}
func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) error {
@ -170,7 +172,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.SQLStore)
dashProvisioner, err := ps.newDashboardProvisioner(ctx, dashboardPath, ps.dashboardsStore)
if err != nil {
return errutil.Wrap("Failed to create provisioner", err)
}

View File

@ -6,7 +6,7 @@ import (
"testing"
"time"
dboards "github.com/grafana/grafana/pkg/dashboards"
dashboardstore "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
@ -92,7 +92,7 @@ func setup() *serviceTestStruct {
}
serviceTest.service = newProvisioningServiceImpl(
func(context.Context, string, dboards.Store) (dashboards.DashboardProvisioner, error) {
func(context.Context, string, dashboardstore.Store) (dashboards.DashboardProvisioner, error) {
return serviceTest.mock, nil
},
nil,

View File

@ -4,18 +4,14 @@ import (
"context"
"fmt"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/util"
"github.com/prometheus/client_golang/prometheus"
)
var shadowSearchCounter = prometheus.NewCounterVec(
@ -47,166 +43,6 @@ func (ss *SQLStore) addDashboardQueryAndCommandHandlers() {
var generateNewUid func() string = util.GenerateShortUID
func (ss *SQLStore) SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) {
err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
return saveDashboard(sess, &cmd)
})
return cmd.Result, err
}
func saveDashboard(sess *DBSession, cmd *models.SaveDashboardCommand) error {
dash := cmd.GetDashboardModel()
userId := cmd.UserId
if userId == 0 {
userId = -1
}
if dash.Id > 0 {
var existing models.Dashboard
dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
if err != nil {
return err
}
if !dashWithIdExists {
return models.ErrDashboardNotFound
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if cmd.Overwrite {
dash.SetVersion(existing.Version)
} else {
return models.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && !cmd.Overwrite {
return models.UpdatePluginDashboardError{PluginId: existing.PluginId}
}
}
if dash.Uid == "" {
uid, err := generateNewDashboardUid(sess, dash.OrgId)
if err != nil {
return err
}
dash.SetUid(uid)
}
parentVersion := dash.Version
var affectedRows int64
var err error
if dash.Id == 0 {
dash.SetVersion(1)
dash.Created = time.Now()
dash.CreatedBy = userId
dash.Updated = time.Now()
dash.UpdatedBy = userId
metrics.MApiDashboardInsert.Inc()
affectedRows, err = sess.Insert(dash)
} else {
dash.SetVersion(dash.Version + 1)
if !cmd.UpdatedAt.IsZero() {
dash.Updated = cmd.UpdatedAt
} else {
dash.Updated = time.Now()
}
dash.UpdatedBy = userId
affectedRows, err = sess.MustCols("folder_id").ID(dash.Id).Update(dash)
}
if err != nil {
return err
}
if affectedRows == 0 {
return models.ErrDashboardNotFound
}
dashVersion := &models.DashboardVersion{
DashboardId: dash.Id,
ParentVersion: parentVersion,
RestoredFrom: cmd.RestoredFrom,
Version: dash.Version,
Created: time.Now(),
CreatedBy: dash.UpdatedBy,
Message: cmd.Message,
Data: dash.Data,
}
// insert version entry
if affectedRows, err = sess.Insert(dashVersion); err != nil {
return err
} else if affectedRows == 0 {
return models.ErrDashboardNotFound
}
// delete existing tags
_, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
if err != nil {
return err
}
// insert new tags
tags := dash.GetTags()
if len(tags) > 0 {
for _, tag := range tags {
if _, err := sess.Insert(&DashboardTag{DashboardId: dash.Id, Term: tag}); err != nil {
return err
}
}
}
cmd.Result = dash
return nil
}
func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
for i := 0; i < 3; i++ {
uid := generateNewUid()
exists, err := sess.Where("org_id=? AND uid=?", orgId, uid).Get(&models.Dashboard{})
if err != nil {
return "", err
}
if !exists {
return uid, nil
}
}
return "", models.ErrDashboardFailedGenerateUniqueUid
}
// GetDashboardByTitle gets a dashboard by its title.
func (ss *SQLStore) GetFolderByTitle(orgID int64, title string) (*models.Dashboard, error) {
if title == "" {
return nil, models.ErrDashboardIdentifierNotSet
}
// there is a unique constraint on org_id, folder_id, title
// there are no nested folders so the parent folder id is always 0
dashboard := models.Dashboard{OrgId: orgID, FolderId: 0, Title: title}
has, err := ss.engine.Table(&models.Dashboard{}).Where("is_folder = " + dialect.BooleanStr(true)).Where("folder_id=0").Get(&dashboard)
if err != nil {
return nil, err
}
if !has {
return nil, models.ErrDashboardNotFound
}
dashboard.SetId(dashboard.Id)
dashboard.SetUid(dashboard.Uid)
return &dashboard, nil
}
func (ss *SQLStore) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error {
return withDbSession(ctx, x, func(dbSession *DBSession) error {
if query.Id == 0 && len(query.Slug) == 0 && len(query.Uid) == 0 {
@ -243,7 +79,7 @@ type DashboardSearchProjection struct {
SortMeta int64
}
func (ss *SQLStore) findDashboards(ctx context.Context, query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
func (ss *SQLStore) FindDashboards(ctx context.Context, query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
filters := []interface{}{
permissions.DashboardPermissionFilter{
OrgRole: query.SignedInUser.OrgRole,
@ -315,7 +151,7 @@ func (ss *SQLStore) findDashboards(ctx context.Context, query *search.FindPersis
}
func (ss *SQLStore) SearchDashboards(ctx context.Context, query *search.FindPersistedDashboardsQuery) error {
res, err := ss.findDashboards(ctx, query)
res, err := ss.FindDashboards(ctx, query)
if err != nil {
return err
}
@ -619,155 +455,6 @@ func (ss *SQLStore) GetDashboardUIDById(ctx context.Context, query *models.GetDa
})
}
func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, dash *models.Dashboard, overwrite bool) (bool, error) {
dashWithIdExists := false
isParentFolderChanged := false
var existingById models.Dashboard
if dash.Id > 0 {
var err error
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
if err != nil {
return isParentFolderChanged, fmt.Errorf("SQL query for existing dashboard by ID failed: %w", err)
}
if !dashWithIdExists {
return isParentFolderChanged, models.ErrDashboardNotFound
}
if dash.Uid == "" {
dash.SetUid(existingById.Uid)
}
}
dashWithUidExists := false
var existingByUid models.Dashboard
if dash.Uid != "" {
var err error
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
if err != nil {
return isParentFolderChanged, fmt.Errorf("SQL query for existing dashboard by UID failed: %w", err)
}
}
if dash.FolderId > 0 {
var existingFolder models.Dashboard
folderExists, err := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId,
dialect.BooleanStr(true)).Get(&existingFolder)
if err != nil {
return isParentFolderChanged, fmt.Errorf("SQL query for folder failed: %w", err)
}
if !folderExists {
return isParentFolderChanged, models.ErrDashboardFolderNotFound
}
}
if !dashWithIdExists && !dashWithUidExists {
return isParentFolderChanged, nil
}
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
return isParentFolderChanged, models.ErrDashboardWithSameUIDExists
}
existing := existingById
if !dashWithIdExists && dashWithUidExists {
dash.SetId(existingByUid.Id)
dash.SetUid(existingByUid.Uid)
existing = existingByUid
if !dash.IsFolder {
isParentFolderChanged = true
}
}
if (existing.IsFolder && !dash.IsFolder) ||
(!existing.IsFolder && dash.IsFolder) {
return isParentFolderChanged, models.ErrDashboardTypeMismatch
}
if !dash.IsFolder && dash.FolderId != existing.FolderId {
isParentFolderChanged = true
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if overwrite {
dash.SetVersion(existing.Version)
} else {
return isParentFolderChanged, models.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && !overwrite {
return isParentFolderChanged, models.UpdatePluginDashboardError{PluginId: existing.PluginId}
}
return isParentFolderChanged, nil
}
func getExistingDashboardByTitleAndFolder(sess *DBSession, dash *models.Dashboard, overwrite,
isParentFolderChanged bool) (bool, error) {
var existing models.Dashboard
exists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug,
dialect.BooleanStr(true), dash.FolderId).Get(&existing)
if err != nil {
return isParentFolderChanged, fmt.Errorf("SQL query for existing dashboard by org ID or folder ID failed: %w", err)
}
if exists && dash.Id != existing.Id {
if existing.IsFolder && !dash.IsFolder {
return isParentFolderChanged, models.ErrDashboardWithSameNameAsFolder
}
if !existing.IsFolder && dash.IsFolder {
return isParentFolderChanged, models.ErrDashboardFolderWithSameNameAsDashboard
}
if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) {
isParentFolderChanged = true
}
if overwrite {
dash.SetId(existing.Id)
dash.SetUid(existing.Uid)
dash.SetVersion(existing.Version)
} else {
return isParentFolderChanged, models.ErrDashboardWithSameNameInFolderExists
}
}
return isParentFolderChanged, nil
}
func (ss *SQLStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
isParentFolderChanged := false
err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
var err error
isParentFolderChanged, err = getExistingDashboardByIdOrUidForUpdate(sess, dashboard, overwrite)
if err != nil {
return err
}
isParentFolderChanged, err = getExistingDashboardByTitleAndFolder(sess, dashboard, overwrite,
isParentFolderChanged)
if err != nil {
return err
}
return nil
})
if err != nil {
return false, err
}
return isParentFolderChanged, nil
}
// HasEditPermissionInFolders validates that an user have access to a certain folder
func (ss *SQLStore) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error {
return withDbSession(ctx, x, func(dbSession *DBSession) error {

View File

@ -2,7 +2,6 @@ package sqlstore
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
@ -12,40 +11,6 @@ func (ss *SQLStore) addDashboardACLQueryAndCommandHandlers() {
bus.AddHandler("sql", ss.GetDashboardAclInfoList)
}
func (ss *SQLStore) UpdateDashboardACL(ctx context.Context, dashboardID int64, items []*models.DashboardAcl) error {
return ss.UpdateDashboardACLCtx(ctx, dashboardID, items)
}
func (ss *SQLStore) UpdateDashboardACLCtx(ctx context.Context, dashboardID int64, items []*models.DashboardAcl) error {
return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error {
// delete existing items
_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", dashboardID)
if err != nil {
return fmt.Errorf("deleting from dashboard_acl failed: %w", err)
}
for _, item := range items {
if item.UserID == 0 && item.TeamID == 0 && (item.Role == nil || !item.Role.IsValid()) {
return models.ErrDashboardAclInfoMissing
}
if item.DashboardID == 0 {
return models.ErrDashboardPermissionDashboardEmpty
}
sess.Nullable("user_id", "team_id")
if _, err := sess.Insert(item); err != nil {
return err
}
}
// Update dashboard HasAcl flag
dashboard := models.Dashboard{HasAcl: true}
_, err = sess.Cols("has_acl").Where("id=?", dashboardID).Update(&dashboard)
return err
})
}
// GetDashboardAclInfoList returns a list of permissions for a dashboard. They can be fetched from three
// different places.
// 1) Permissions for the dashboard

View File

@ -9,7 +9,6 @@ import (
)
func (ss *SQLStore) addDashboardProvisioningQueryAndCommandHandlers() {
bus.AddHandler("sql", UnprovisionDashboard)
bus.AddHandler("sql", ss.DeleteOrphanedProvisionedDashboards)
}
@ -20,97 +19,6 @@ type DashboardExtras struct {
Value string
}
func (ss *SQLStore) GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
var data models.DashboardProvisioning
exists, err := x.Where("dashboard_id = ?", dashboardID).Get(&data)
if err != nil {
return nil, err
}
if exists {
return &data, nil
}
return nil, nil
}
func (ss *SQLStore) GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error) {
var provisionedDashboard models.DashboardProvisioning
err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
var dashboard models.Dashboard
exists, err := sess.Where("org_id = ? AND uid = ?", orgID, dashboardUID).Get(&dashboard)
if err != nil {
return err
}
if !exists {
return models.
ErrDashboardNotFound
}
exists, err = sess.Where("dashboard_id = ?", dashboard.Id).Get(&provisionedDashboard)
if err != nil {
return err
}
if !exists {
return models.ErrProvisionedDashboardNotFound
}
return nil
})
return &provisionedDashboard, err
}
func (ss *SQLStore) SaveProvisionedDashboard(cmd models.SaveDashboardCommand,
provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
err := ss.WithTransactionalDbSession(context.Background(), func(sess *DBSession) error {
if err := saveDashboard(sess, &cmd); err != nil {
return err
}
if provisioning.Updated == 0 {
provisioning.Updated = cmd.Result.Updated.Unix()
}
return saveProvisionedData(sess, provisioning, cmd.Result)
})
return cmd.Result, err
}
func saveProvisionedData(sess *DBSession, provisioning *models.DashboardProvisioning, dashboard *models.Dashboard) error {
result := &models.DashboardProvisioning{}
exist, err := sess.Where("dashboard_id=? AND name = ?", dashboard.Id, provisioning.Name).Get(result)
if err != nil {
return err
}
provisioning.Id = result.Id
provisioning.DashboardId = dashboard.Id
if exist {
_, err = sess.ID(result.Id).Update(provisioning)
} else {
_, err = sess.Insert(provisioning)
}
return err
}
func (ss *SQLStore) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
var result []*models.DashboardProvisioning
if err := ss.engine.Where("name = ?", name).Find(&result); err != nil {
return nil, err
}
return result, nil
}
// UnprovisionDashboard removes row in dashboard_provisioning for the dashboard making it seem as if manually created.
// The dashboard will still have `created_by = -1` to see it was not created by any particular user.
func UnprovisionDashboard(ctx context.Context, cmd *models.UnprovisionDashboardCommand) error {
if _, err := x.Where("dashboard_id = ?", cmd.Id).Delete(&models.DashboardProvisioning{}); err != nil {
return err
}
return nil
}
func (ss *SQLStore) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
var result []*models.DashboardProvisioning

View File

@ -7,10 +7,12 @@ import (
"context"
"reflect"
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/require"
)
@ -19,13 +21,60 @@ func updateTestDashboard(t *testing.T, sqlStore *SQLStore, dashboard *models.Das
data["id"] = dashboard.Id
saveCmd := models.SaveDashboardCommand{
parentVersion := dashboard.Version
cmd := models.SaveDashboardCommand{
OrgId: dashboard.OrgId,
Overwrite: true,
Dashboard: simplejson.NewFromAny(data),
}
_, err := sqlStore.SaveDashboard(saveCmd)
var dash *models.Dashboard
err := sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
var existing models.Dashboard
dash = cmd.GetDashboardModel()
dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
require.NoError(t, err)
require.True(t, dashWithIdExists)
if dash.Version != existing.Version {
dash.SetVersion(existing.Version)
dash.Version = existing.Version
}
dash.SetVersion(dash.Version + 1)
dash.Created = time.Now()
dash.Updated = time.Now()
dash.Id = dashboard.Id
dash.Uid = util.GenerateShortUID()
_, err = sess.MustCols("folder_id").ID(dash.Id).Update(dash)
return err
})
require.Nil(t, err)
err = sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
dashVersion := &models.DashboardVersion{
DashboardId: dash.Id,
ParentVersion: parentVersion,
RestoredFrom: cmd.RestoredFrom,
Version: dash.Version,
Created: time.Now(),
CreatedBy: dash.UpdatedBy,
Message: cmd.Message,
Data: dash.Data,
}
if affectedRows, err := sess.Insert(dashVersion); err != nil {
return err
} else if affectedRows == 0 {
return models.ErrDashboardNotFound
}
return nil
})
require.NoError(t, err)
}
func TestGetDashboardVersion(t *testing.T) {
@ -94,7 +143,6 @@ func TestGetDashboardVersions(t *testing.T) {
updateTestDashboard(t, sqlStore, savedDash, map[string]interface{}{
"tags": "different-tag",
})
query := models.GetDashboardVersionsQuery{DashboardId: savedDash.Id, OrgId: 1}
err := sqlStore.GetDashboardVersions(context.Background(), &query)

View File

@ -112,23 +112,7 @@ func (m *SQLStoreMock) DeleteOrg(ctx context.Context, cmd *models.DeleteOrgComma
return m.ExpectedError
}
func (m *SQLStoreMock) GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) {
return &models.DashboardProvisioning{}, m.ExpectedError
}
func (m *SQLStoreMock) GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error) {
return nil, m.ExpectedError
}
func (m *SQLStoreMock) SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
return nil, m.ExpectedError
}
func (m *SQLStoreMock) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
return nil, m.ExpectedError
}
func (m *SQLStoreMock) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
func (m SQLStoreMock) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
return m.ExpectedError
}
@ -382,16 +366,7 @@ func (m *SQLStoreMock) DeleteExpiredVersions(ctx context.Context, cmd *models.De
return m.ExpectedError
}
func (m *SQLStoreMock) UpdateDashboardACL(ctx context.Context, dashboardID int64, items []*models.DashboardAcl) error {
return m.ExpectedError
}
func (m *SQLStoreMock) UpdateDashboardACLCtx(ctx context.Context, dashboardID int64, items []*models.DashboardAcl) error {
return m.ExpectedError
}
func (m *SQLStoreMock) GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
query.Result = m.ExpectedDashboardAclInfoList
func (m SQLStoreMock) GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
return m.ExpectedError
}
@ -433,11 +408,7 @@ func (m *SQLStoreMock) HandleAlertsQuery(ctx context.Context, query *models.GetA
return m.ExpectedError
}
func (m *SQLStoreMock) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error {
return m.ExpectedError
}
func (m *SQLStoreMock) SetAlertState(ctx context.Context, cmd *models.SetAlertStateCommand) error {
func (m SQLStoreMock) SetAlertState(ctx context.Context, cmd *models.SetAlertStateCommand) error {
return m.ExpectedError
}
@ -484,18 +455,14 @@ func (m *SQLStoreMock) GetDashboard(ctx context.Context, query *models.GetDashbo
return m.ExpectedError
}
func (m SQLStoreMock) SearchDashboards(ctx context.Context, query *search.FindPersistedDashboardsQuery) error {
return m.ExpectedError
}
func (m *SQLStoreMock) GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error {
return nil // TODO: Implement
}
func (m *SQLStoreMock) GetFolderByTitle(orgID int64, title string) (*models.Dashboard, error) {
return nil, m.ExpectedError
}
func (m *SQLStoreMock) SearchDashboards(ctx context.Context, query *search.FindPersistedDashboardsQuery) error {
return m.ExpectedError
}
func (m *SQLStoreMock) DeleteDashboard(ctx context.Context, cmd *models.DeleteDashboardCommand) error {
cmd.Id = m.ExpectedDashboard.Id
cmd.OrgId = m.ExpectedDashboard.OrgId
@ -510,11 +477,7 @@ func (m *SQLStoreMock) GetDashboardUIDById(ctx context.Context, query *models.Ge
return m.ExpectedError
}
func (m *SQLStoreMock) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) {
return false, nil
}
func (m *SQLStoreMock) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error {
func (m SQLStoreMock) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error {
query.Result = m.ExpectedDatasource
return m.ExpectedError
}

View File

@ -9,8 +9,10 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/require"
)
@ -329,12 +331,12 @@ func TestAccountDataAccess(t *testing.T) {
dash1 := insertTestDashboard(t, sqlStore, "1 test dash", ac1.OrgId, 0, false, "prod", "webapp")
dash2 := insertTestDashboard(t, sqlStore, "2 test dash", ac3.OrgId, 0, false, "prod", "webapp")
err = testHelperUpdateDashboardAcl(t, sqlStore, dash1.Id, models.DashboardAcl{
err = updateDashboardAcl(t, sqlStore, dash1.Id, &models.DashboardAcl{
DashboardID: dash1.Id, OrgID: ac1.OrgId, UserID: ac3.Id, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
err = testHelperUpdateDashboardAcl(t, sqlStore, dash2.Id, models.DashboardAcl{
err = updateDashboardAcl(t, sqlStore, dash2.Id, &models.DashboardAcl{
DashboardID: dash2.Id, OrgID: ac3.OrgId, UserID: ac3.Id, Permission: models.PERMISSION_EDIT,
})
require.NoError(t, err)
@ -370,16 +372,92 @@ func TestAccountDataAccess(t *testing.T) {
})
}
func testHelperUpdateDashboardAcl(t *testing.T, sqlStore *SQLStore, dashboardID int64,
items ...models.DashboardAcl) error {
//TODO: Use FakeDashboardStore when org has its own service
func insertTestDashboard(t *testing.T, sqlStore *SQLStore, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
t.Helper()
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: isFolder,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
"tags": tags,
}),
}
var dash *models.Dashboard
err := sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
dash = cmd.GetDashboardModel()
dash.SetVersion(1)
dash.Created = time.Now()
dash.Updated = time.Now()
dash.Uid = util.GenerateShortUID()
_, err := sess.Insert(dash)
return err
})
require.NoError(t, err)
require.NotNil(t, dash)
dash.Data.Set("id", dash.Id)
dash.Data.Set("uid", dash.Uid)
err = sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
dashVersion := &models.DashboardVersion{
DashboardId: dash.Id,
ParentVersion: dash.Version,
RestoredFrom: cmd.RestoredFrom,
Version: dash.Version,
Created: time.Now(),
CreatedBy: dash.UpdatedBy,
Message: cmd.Message,
Data: dash.Data,
}
if affectedRows, err := sess.Insert(dashVersion); err != nil {
return err
} else if affectedRows == 0 {
return models.ErrDashboardNotFound
}
return nil
})
return dash
}
//TODO: Use FakeDashboardStore when org has its own service
func updateDashboardAcl(t *testing.T, sqlStore *SQLStore, dashboardID int64, items ...*models.DashboardAcl) error {
t.Helper()
var itemPtrs []*models.DashboardAcl
for _, it := range items {
item := it
item.Created = time.Now()
item.Updated = time.Now()
itemPtrs = append(itemPtrs, &item)
}
return sqlStore.UpdateDashboardACL(context.Background(), dashboardID, itemPtrs)
err := sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", dashboardID)
if err != nil {
return fmt.Errorf("deleting from dashboard_acl failed: %w", err)
}
for _, item := range items {
item.Created = time.Now()
item.Updated = time.Now()
if item.UserID == 0 && item.TeamID == 0 && (item.Role == nil || !item.Role.IsValid()) {
return models.ErrDashboardAclInfoMissing
}
if item.DashboardID == 0 {
return models.ErrDashboardPermissionDashboardEmpty
}
sess.Nullable("user_id", "team_id")
if _, err := sess.Insert(item); err != nil {
return err
}
}
// Update dashboard HasAcl flag
dashboard := models.Dashboard{HasAcl: true}
_, err = sess.Cols("has_acl").Where("id=?", dashboardID).Update(&dashboard)
return err
})
return err
}

View File

@ -1,19 +1,16 @@
//go:build integration
// +build integration
// package search_test contains integration tests for search
package searchstore_test
import (
"context"
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -171,11 +168,27 @@ func createDashboards(t *testing.T, db *sqlstore.SQLStore, startID, endID int, o
"version": 0
}`))
require.NoError(t, err)
dash, err := db.SaveDashboard(models.SaveDashboardCommand{
Dashboard: dashboard,
UserId: 1,
OrgId: orgID,
UpdatedAt: time.Now(),
var dash *models.Dashboard
err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
dash = models.NewDashboardFromJson(dashboard)
dash.OrgId = orgID
dash.Uid = util.GenerateShortUID()
dash.CreatedBy = 1
dash.UpdatedBy = 1
_, err := sess.Insert(dash)
require.NoError(t, err)
tags := dash.GetTags()
if len(tags) > 0 {
for _, tag := range tags {
if _, err := sess.Insert(&sqlstore.DashboardTag{DashboardId: dash.Id, Term: tag}); err != nil {
return err
}
}
}
return nil
})
require.NoError(t, err)

View File

@ -244,7 +244,7 @@ func createDummyDashboard(t *testing.T, sqlStore *SQLStore, dashboardProps Dashb
saveDashboardCmd.OrgId = 1
}
dash, err := sqlStore.SaveDashboard(saveDashboardCmd)
dash := insertTestDashboard(t, sqlStore, "", saveDashboardCmd.OrgId, 0, false, nil)
require.NoError(t, err)
t.Logf("Created dashboard with ID %d and org ID %d\n", dash.Id, dash.OrgId)
@ -287,7 +287,7 @@ func createDummyACL(t *testing.T, sqlStore *SQLStore, dashboardPermission *Dashb
acl.Role = &dashboardPermission.Role
}
err := sqlStore.UpdateDashboardACL(context.Background(), dashboardID, []*models.DashboardAcl{acl})
err := updateDashboardAcl(t, sqlStore, dashboardID, acl)
require.NoError(t, err)
if user != nil {
return user.Id

View File

@ -24,10 +24,6 @@ type Store interface {
UpdateOrg(ctx context.Context, cmd *models.UpdateOrgCommand) error
UpdateOrgAddress(ctx context.Context, cmd *models.UpdateOrgAddressCommand) error
DeleteOrg(ctx context.Context, cmd *models.DeleteOrgCommand) error
GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error)
SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error)
DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error
CreateLoginAttempt(ctx context.Context, cmd *models.CreateLoginAttemptCommand) error
DeleteOldLoginAttempts(ctx context.Context, cmd *models.DeleteOldLoginAttemptsCommand) error
@ -87,8 +83,6 @@ type Store interface {
GetDashboardVersion(ctx context.Context, query *models.GetDashboardVersionQuery) error
GetDashboardVersions(ctx context.Context, query *models.GetDashboardVersionsQuery) error
DeleteExpiredVersions(ctx context.Context, cmd *models.DeleteExpiredVersionsCommand) error
UpdateDashboardACL(ctx context.Context, dashboardID int64, items []*models.DashboardAcl) error
UpdateDashboardACLCtx(ctx context.Context, dashboardID int64, items []*models.DashboardAcl) error
GetDashboardAclInfoList(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error
CreatePlaylist(ctx context.Context, cmd *models.CreatePlaylistCommand) error
UpdatePlaylist(ctx context.Context, cmd *models.UpdatePlaylistCommand) error
@ -99,7 +93,6 @@ type Store interface {
GetAlertById(ctx context.Context, query *models.GetAlertByIdQuery) error
GetAllAlertQueryHandler(ctx context.Context, query *models.GetAllAlertsQuery) error
HandleAlertsQuery(ctx context.Context, query *models.GetAlertsQuery) error
SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error
SetAlertState(ctx context.Context, cmd *models.SetAlertStateCommand) error
PauseAlert(ctx context.Context, cmd *models.PauseAlertCommand) error
PauseAllAlerts(ctx context.Context, cmd *models.PauseAllAlertCommand) error
@ -109,15 +102,12 @@ type Store interface {
GetOrgUsers(ctx context.Context, query *models.GetOrgUsersQuery) error
SearchOrgUsers(ctx context.Context, query *models.SearchOrgUsersQuery) error
RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgUserCommand) error
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)
GetDashboard(ctx context.Context, query *models.GetDashboardQuery) error
GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error
GetFolderByTitle(orgID int64, title string) (*models.Dashboard, error)
SearchDashboards(ctx context.Context, query *search.FindPersistedDashboardsQuery) error
DeleteDashboard(ctx context.Context, cmd *models.DeleteDashboardCommand) error
GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error
GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error
ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error)
GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error
GetDataSources(ctx context.Context, query *models.GetDataSourcesQuery) error
GetDataSourcesByType(ctx context.Context, query *models.GetDataSourcesByTypeQuery) error

View File

@ -274,7 +274,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
require.NoError(t, err)
err = sqlStore.AddTeamMember(userIds[2], testOrgID, groupId, false, 0)
require.NoError(t, err)
err = testHelperUpdateDashboardAcl(t, sqlStore, 1, models.DashboardAcl{
err = updateDashboardAcl(t, sqlStore, 1, &models.DashboardAcl{
DashboardID: 1, OrgID: testOrgID, Permission: models.PERMISSION_EDIT, TeamID: groupId,
})
require.NoError(t, err)

View File

@ -239,7 +239,7 @@ func TestUserDataAccess(t *testing.T) {
})
require.Nil(t, err)
err = testHelperUpdateDashboardAcl(t, ss, 1, models.DashboardAcl{
err = updateDashboardAcl(t, ss, 1, &models.DashboardAcl{
DashboardID: 1, OrgID: users[0].OrgId, UserID: users[1].Id,
Permission: models.PERMISSION_EDIT,
})
@ -290,7 +290,7 @@ func TestUserDataAccess(t *testing.T) {
})
require.Nil(t, err)
err = testHelperUpdateDashboardAcl(t, ss, 1, models.DashboardAcl{
err = updateDashboardAcl(t, ss, 1, &models.DashboardAcl{
DashboardID: 1, OrgID: users[0].OrgId, UserID: users[1].Id,
Permission: models.PERMISSION_EDIT,
})

View File

@ -12,15 +12,15 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards/database"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
@ -2631,7 +2631,8 @@ func createFolder(t *testing.T, store *sqlstore.SQLStore, folderID int64, folder
"title": folderName,
}),
}
f, err := store.SaveDashboard(cmd)
dashboardsStore := database.ProvideDashboardStore(store)
f, err := dashboardsStore.SaveDashboard(cmd)
if err != nil {
return "", err

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models"
dashboardsstore "github.com/grafana/grafana/pkg/services/dashboards/database"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/tests/testinfra"
@ -634,6 +635,7 @@ func TestPrometheusRulesPermissions(t *testing.T) {
})
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
dashboardsStore := dashboardsstore.ProvideDashboardStore(store)
// override bus to get the GetSignedInUserQuery handler
store.Bus = bus.GetBus()
@ -729,7 +731,7 @@ func TestPrometheusRulesPermissions(t *testing.T) {
}
// remove permissions from folder2
require.NoError(t, store.UpdateDashboardACL(context.Background(), 2, nil))
require.NoError(t, dashboardsStore.UpdateDashboardACL(context.Background(), 2, nil))
// make sure that folder2 is not included in the response
{
@ -778,7 +780,7 @@ func TestPrometheusRulesPermissions(t *testing.T) {
}
// remove permissions from _ALL_ folders
require.NoError(t, store.UpdateDashboardACL(context.Background(), 1, nil))
require.NoError(t, dashboardsStore.UpdateDashboardACL(context.Background(), 1, nil))
// make sure that no folders are included in the response
{

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models"
dashboardsstore "github.com/grafana/grafana/pkg/services/dashboards/database"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/tests/testinfra"
@ -34,6 +35,7 @@ func TestAlertRulePermissions(t *testing.T) {
})
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
dashboardsStore := dashboardsstore.ProvideDashboardStore(store)
// override bus to get the GetSignedInUserQuery handler
store.Bus = bus.GetBus()
@ -180,7 +182,7 @@ func TestAlertRulePermissions(t *testing.T) {
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
// remove permissions from folder2
require.NoError(t, store.UpdateDashboardACL(context.Background(), 2, nil))
require.NoError(t, dashboardsStore.UpdateDashboardACL(context.Background(), 2, nil))
// make sure that folder2 is not included in the response
// nolint:gosec
@ -253,7 +255,7 @@ func TestAlertRulePermissions(t *testing.T) {
}
// Remove permissions from ALL folders.
require.NoError(t, store.UpdateDashboardACL(context.Background(), 1, nil))
require.NoError(t, dashboardsStore.UpdateDashboardACL(context.Background(), 1, nil))
{
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
// nolint:gosec