mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
4393992775
commit
d5b98772ed
@ -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)
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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: "a.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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
@ -456,7 +456,3 @@ type GetDashboardRefByIdQuery struct {
|
||||
Id int64
|
||||
Result *DashboardRef
|
||||
}
|
||||
|
||||
type UnprovisionDashboardCommand struct {
|
||||
Id int64
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
47
pkg/services/dashboards/dashboard.go
Normal file
47
pkg/services/dashboards/dashboard.go
Normal 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
|
||||
}
|
158
pkg/services/dashboards/dashboard_provisioning_mock.go
Normal file
158
pkg/services/dashboards/dashboard_provisioning_mock.go
Normal 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
|
||||
}
|
44
pkg/services/dashboards/dashboard_service_mock.go
Normal file
44
pkg/services/dashboards/dashboard_service_mock.go
Normal 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
|
||||
}
|
611
pkg/services/dashboards/database/database.go
Normal file
611
pkg/services/dashboards/database/database.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
217
pkg/services/dashboards/database/database_mock.go
Normal file
217
pkg/services/dashboards/database/database_mock.go
Normal 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
|
||||
}
|
@ -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)
|
||||
})
|
@ -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)
|
||||
}
|
20
pkg/services/dashboards/folder.go
Normal file
20
pkg/services/dashboards/folder.go
Normal 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
|
||||
}
|
181
pkg/services/dashboards/folder_service_mock.go
Normal file
181
pkg/services/dashboards/folder_service_mock.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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,
|
@ -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
|
||||
}
|
@ -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)
|
@ -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)
|
16
pkg/services/dashboards/models.go
Normal file
16
pkg/services/dashboards/models.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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})
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user