Pubdash: Email sharing handle dashboard deleted (#64247)

dashboard service calls pubdash service when dashboard deleted
This commit is contained in:
owensmallwood 2023-03-08 14:54:35 -06:00 committed by GitHub
parent 154fa2dd00
commit 1a5a280c86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 251 additions and 152 deletions

View File

@ -326,6 +326,12 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo
hs.log.Error("Failed to disconnect library elements", "dashboard", dash.ID, "user", c.SignedInUser.UserID, "error", err) hs.log.Error("Failed to disconnect library elements", "dashboard", dash.ID, "user", c.SignedInUser.UserID, "error", err)
} }
// deletes all related public dashboard entities
err = hs.PublicDashboardsApi.PublicDashboardService.DeleteByDashboard(c.Req.Context(), dash)
if err != nil {
hs.log.Error("Failed to delete public dashboard")
}
err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, c.OrgID) err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.ID, c.OrgID)
if err != nil { if err != nil {
var dashboardErr dashboards.DashboardErr var dashboardErr dashboards.DashboardErr

View File

@ -9,6 +9,8 @@ import (
"os" "os"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/publicdashboards/api"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -304,7 +306,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUp() setUp()
sc.sqlStore = mockSQLStore sc.sqlStore = mockSQLStore
hs.callDeleteDashboardByUID(t, sc, dashboardService) hs.callDeleteDashboardByUID(t, sc, dashboardService, nil)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
}, mockSQLStore) }, mockSQLStore)
@ -342,7 +344,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi",
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUp() setUp()
hs.callDeleteDashboardByUID(t, sc, dashboardService) hs.callDeleteDashboardByUID(t, sc, dashboardService, nil)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
}, mockSQLStore) }, mockSQLStore)
@ -400,23 +402,9 @@ func TestDashboardAPIEndpoint(t *testing.T) {
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(qResult, nil) dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(qResult, nil)
dashboardService.On("DeleteDashboard", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(nil) dashboardService.On("DeleteDashboard", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(nil)
hs.callDeleteDashboardByUID(t, sc, dashboardService) pubdashService := publicdashboards.NewFakePublicDashboardService(t)
pubdashService.On("DeleteByDashboard", mock.Anything, mock.Anything).Return(nil)
assert.Equal(t, 200, sc.resp.Code) hs.callDeleteDashboardByUID(t, sc, dashboardService, pubdashService)
}, mockSQLStore)
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
setUpInner()
sc.sqlStore = mockSQLStore
sc.dashboardVersionService = fakeDashboardVersionService
hs.callGetDashboardVersion(sc)
assert.Equal(t, 200, sc.resp.Code)
}, mockSQLStore)
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
setUpInner()
hs.callGetDashboardVersions(sc)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
}, mockSQLStore) }, mockSQLStore)
@ -455,7 +443,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUpInner() setUpInner()
hs.callDeleteDashboardByUID(t, sc, dashboardService) hs.callDeleteDashboardByUID(t, sc, dashboardService, nil)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
}, mockSQLStore) }, mockSQLStore)
}) })
@ -495,7 +483,9 @@ func TestDashboardAPIEndpoint(t *testing.T) {
qResult := dashboards.NewDashboard("test") qResult := dashboards.NewDashboard("test")
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(qResult, nil) dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(qResult, nil)
dashboardService.On("DeleteDashboard", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(nil) dashboardService.On("DeleteDashboard", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(nil)
hs.callDeleteDashboardByUID(t, sc, dashboardService) pubdashService := publicdashboards.NewFakePublicDashboardService(t)
pubdashService.On("DeleteByDashboard", mock.Anything, mock.Anything).Return(nil)
hs.callDeleteDashboardByUID(t, sc, dashboardService, pubdashService)
assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, 200, sc.resp.Code)
}, mockSQLStore) }, mockSQLStore)
@ -538,7 +528,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
setUpInner() setUpInner()
hs.callDeleteDashboardByUID(t, sc, dashboardService) hs.callDeleteDashboardByUID(t, sc, dashboardService, nil)
assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, 403, sc.resp.Code)
}, mockSQLStore) }, mockSQLStore)
@ -1035,8 +1025,10 @@ func (hs *HTTPServer) callGetDashboardVersions(sc *scenarioContext) {
} }
func (hs *HTTPServer) callDeleteDashboardByUID(t *testing.T, func (hs *HTTPServer) callDeleteDashboardByUID(t *testing.T,
sc *scenarioContext, mockDashboard *dashboards.FakeDashboardService) { sc *scenarioContext, mockDashboard *dashboards.FakeDashboardService, mockPubdashService *publicdashboards.FakePublicDashboardService) {
hs.DashboardService = mockDashboard hs.DashboardService = mockDashboard
pubdashApi := api.ProvideApi(mockPubdashService, nil, nil, featuremgmt.WithFeatures())
hs.PublicDashboardsApi = pubdashApi
sc.handlerFunc = hs.DeleteDashboardByUID sc.handlerFunc = hs.DeleteDashboardByUID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
} }

View File

@ -702,7 +702,6 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand,
deletes := []string{ deletes := []string{
"DELETE FROM dashboard_tag WHERE dashboard_id = ? ", "DELETE FROM dashboard_tag WHERE dashboard_id = ? ",
"DELETE FROM star WHERE dashboard_id = ? ", "DELETE FROM star WHERE dashboard_id = ? ",
"DELETE FROM dashboard_public WHERE dashboard_uid = (SELECT uid FROM dashboard WHERE id = ?)",
"DELETE FROM dashboard WHERE id = ?", "DELETE FROM dashboard WHERE id = ?",
"DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?", "DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?",
"DELETE FROM dashboard_version WHERE dashboard_id = ?", "DELETE FROM dashboard_version WHERE dashboard_id = ?",
@ -751,7 +750,6 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand,
"DELETE FROM annotation WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)", "DELETE FROM annotation WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM dashboard_provisioning WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)", "DELETE FROM dashboard_provisioning WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM dashboard_acl WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)", "DELETE FROM dashboard_acl WHERE dashboard_id IN (SELECT id FROM dashboard WHERE org_id = ? AND folder_id = ?)",
"DELETE FROM dashboard_public WHERE dashboard_uid IN (SELECT uid FROM dashboard WHERE org_id = ? AND folder_id = ?)",
} }
for _, sql := range childrenDeletes { for _, sql := range childrenDeletes {
_, err := sess.Exec(sql, dashboard.OrgID, dashboard.ID) _, err := sess.Exec(sql, dashboard.OrgID, dashboard.ID)

View File

@ -16,8 +16,6 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/publicdashboards/database"
publicDashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
"github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/model" "github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
@ -27,7 +25,6 @@ import (
"github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
) )
func TestIntegrationDashboardDataAccess(t *testing.T) { func TestIntegrationDashboardDataAccess(t *testing.T) {
@ -39,7 +36,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
var savedFolder, savedDash, savedDash2 *dashboards.Dashboard var savedFolder, savedDash, savedDash2 *dashboards.Dashboard
var dashboardStore dashboards.Store var dashboardStore dashboards.Store
var starService star.Service var starService star.Service
var publicDashboardStore *database.PublicDashboardStoreImpl
setup := func() { setup := func() {
sqlStore, cfg = db.InitTestDBwithCfg(t) sqlStore, cfg = db.InitTestDBwithCfg(t)
@ -53,8 +49,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.ID, false, "prod") insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.ID, false, "prod")
savedDash2 = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod") savedDash2 = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod")
insertTestRule(t, sqlStore, savedFolder.OrgID, savedFolder.UID) insertTestRule(t, sqlStore, savedFolder.OrgID, savedFolder.UID)
publicDashboardStore = database.ProvideStore(sqlStore)
} }
t.Run("Should return dashboard model", func(t *testing.T) { t.Run("Should return dashboard model", func(t *testing.T) {
@ -246,78 +240,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
require.True(t, errors.Is(err, dashboards.ErrFolderContainsAlertRules)) require.True(t, errors.Is(err, dashboards.ErrFolderContainsAlertRules))
}) })
t.Run("Should be able to delete dashboard and related public dashboard", func(t *testing.T) {
setup()
uid := util.GenerateShortUID()
cmd := publicDashboardModels.SavePublicDashboardCommand{
PublicDashboard: publicDashboardModels.PublicDashboard{
Uid: uid,
DashboardUid: savedDash.UID,
OrgId: savedDash.OrgID,
IsEnabled: true,
TimeSettings: &publicDashboardModels.TimeSettings{},
CreatedBy: 1,
CreatedAt: time.Now(),
AccessToken: "an-access-token",
},
}
_, err := publicDashboardStore.Create(context.Background(), cmd)
require.NoError(t, err)
pubdashConfig, _ := publicDashboardStore.FindByAccessToken(context.Background(), "an-access-token")
require.NotNil(t, pubdashConfig)
deleteCmd := &dashboards.DeleteDashboardCommand{ID: savedDash.ID, OrgID: savedDash.OrgID}
err = dashboardStore.DeleteDashboard(context.Background(), deleteCmd)
require.NoError(t, err)
query := dashboards.GetDashboardQuery{UID: savedDash.UID, OrgID: savedDash.OrgID}
dash, getErr := dashboardStore.GetDashboard(context.Background(), &query)
require.Equal(t, getErr, dashboards.ErrDashboardNotFound)
assert.Nil(t, dash)
pubdashConfig, err = publicDashboardStore.FindByAccessToken(context.Background(), "an-access-token")
require.Nil(t, err)
require.Nil(t, pubdashConfig)
})
t.Run("Should be able to delete a dashboard folder, with its dashboard and related public dashboard", func(t *testing.T) {
setup()
uid := util.GenerateShortUID()
cmd := publicDashboardModels.SavePublicDashboardCommand{
PublicDashboard: publicDashboardModels.PublicDashboard{
Uid: uid,
DashboardUid: savedDash.UID,
OrgId: savedDash.OrgID,
IsEnabled: true,
TimeSettings: &publicDashboardModels.TimeSettings{},
CreatedBy: 1,
CreatedAt: time.Now(),
AccessToken: "an-access-token",
},
}
_, err := publicDashboardStore.Create(context.Background(), cmd)
require.NoError(t, err)
pubdashConfig, _ := publicDashboardStore.FindByAccessToken(context.Background(), "an-access-token")
require.NotNil(t, pubdashConfig)
deleteCmd := &dashboards.DeleteDashboardCommand{ID: savedFolder.ID, ForceDeleteFolderRules: true}
err = dashboardStore.DeleteDashboard(context.Background(), deleteCmd)
require.NoError(t, err)
query := dashboards.GetDashboardsQuery{
DashboardIDs: []int64{savedFolder.ID, savedDash.ID},
}
queryResult, err := dashboardStore.GetDashboards(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(queryResult), 0)
pubdashConfig, err = publicDashboardStore.FindByAccessToken(context.Background(), "an-access-token")
require.Nil(t, err)
require.Nil(t, pubdashConfig)
})
t.Run("Should be able to delete a dashboard folder and its children if force delete rules is enabled", func(t *testing.T) { t.Run("Should be able to delete a dashboard folder and its children if force delete rules is enabled", func(t *testing.T) {
setup() setup()
deleteCmd := &dashboards.DeleteDashboardCommand{ID: savedFolder.ID, ForceDeleteFolderRules: true} deleteCmd := &dashboards.DeleteDashboardCommand{ID: savedFolder.ID, ForceDeleteFolderRules: true}

View File

@ -196,7 +196,7 @@ func (api *Api) DeletePublicDashboard(c *contextmodel.ReqContext) response.Respo
return response.Err(ErrInvalidUid.Errorf("UpdatePublicDashboard: invalid Uid %s", uid)) return response.Err(ErrInvalidUid.Errorf("UpdatePublicDashboard: invalid Uid %s", uid))
} }
err := api.PublicDashboardService.Delete(c.Req.Context(), c.OrgID, uid) err := api.PublicDashboardService.Delete(c.Req.Context(), uid)
if err != nil { if err != nil {
return response.Err(err) return response.Err(err)
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/publicdashboards"
. "github.com/grafana/grafana/pkg/services/publicdashboards/models" . "github.com/grafana/grafana/pkg/services/publicdashboards/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
) )
// Define the storage implementation. We're generating the mock implementation // Define the storage implementation. We're generating the mock implementation
@ -255,8 +256,8 @@ func (d *PublicDashboardStoreImpl) Update(ctx context.Context, cmd SavePublicDas
} }
// Deletes a public dashboard // Deletes a public dashboard
func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, orgId int64, uid string) (int64, error) { func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, uid string) (int64, error) {
dashboard := &PublicDashboard{OrgId: orgId, Uid: uid} dashboard := &PublicDashboard{Uid: uid}
var affectedRows int64 var affectedRows int64
err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
var err error var err error
@ -267,3 +268,20 @@ func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, orgId int64, uid
return affectedRows, err return affectedRows, err
} }
func (d *PublicDashboardStoreImpl) FindByDashboardFolder(ctx context.Context, dashboard *dashboards.Dashboard) ([]*PublicDashboard, error) {
if dashboard == nil || !dashboard.IsFolder {
return nil, nil
}
var pubdashes []*PublicDashboard
err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
return sess.SQL("SELECT * from dashboard_public WHERE (dashboard_uid, org_id) IN (SELECT uid, org_id FROM dashboard WHERE folder_id = ?)", dashboard.ID).Find(&pubdashes)
})
if err != nil {
return nil, err
}
return pubdashes, nil
}

View File

@ -652,7 +652,7 @@ func TestIntegrationDelete(t *testing.T) {
t.Run("Delete success", func(t *testing.T) { t.Run("Delete success", func(t *testing.T) {
setup() setup()
// Do the deletion // Do the deletion
affectedRows, err := publicdashboardStore.Delete(context.Background(), savedPublicDashboard.OrgId, savedPublicDashboard.Uid) affectedRows, err := publicdashboardStore.Delete(context.Background(), savedPublicDashboard.Uid)
require.NoError(t, err) require.NoError(t, err)
assert.EqualValues(t, affectedRows, 1) assert.EqualValues(t, affectedRows, 1)
@ -665,12 +665,51 @@ func TestIntegrationDelete(t *testing.T) {
t.Run("Non-existent public dashboard deletion doesn't throw an error", func(t *testing.T) { t.Run("Non-existent public dashboard deletion doesn't throw an error", func(t *testing.T) {
setup() setup()
affectedRows, err := publicdashboardStore.Delete(context.Background(), 15, "non-existent-uid") affectedRows, err := publicdashboardStore.Delete(context.Background(), "non-existent-uid")
require.NoError(t, err) require.NoError(t, err)
assert.EqualValues(t, affectedRows, 0) assert.EqualValues(t, affectedRows, 0)
}) })
} }
func TestGetDashboardByFolder(t *testing.T) {
t.Run("returns nil when dashboard is not a folder", func(t *testing.T) {
sqlStore, _ := db.InitTestDBwithCfg(t)
dashboard := &dashboards.Dashboard{IsFolder: false}
store := ProvideStore(sqlStore)
pubdashes, err := store.FindByDashboardFolder(context.Background(), dashboard)
require.NoError(t, err)
assert.Nil(t, pubdashes)
})
t.Run("returns nil when dashboard is nil", func(t *testing.T) {
sqlStore, _ := db.InitTestDBwithCfg(t)
store := ProvideStore(sqlStore)
pubdashes, err := store.FindByDashboardFolder(context.Background(), nil)
require.NoError(t, err)
assert.Nil(t, pubdashes)
})
t.Run("can get all pubdashes for dashboard folder and org", func(t *testing.T) {
sqlStore, cfg := db.InitTestDBwithCfg(t)
quotaService := quotatest.New(false, nil)
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
require.NoError(t, err)
pubdashStore := ProvideStore(sqlStore)
dashboard := insertTestDashboard(t, dashboardStore, "title", 1, 1, true)
pubdash := insertPublicDashboard(t, pubdashStore, dashboard.UID, dashboard.OrgID, true)
dashboard2 := insertTestDashboard(t, dashboardStore, "title", 1, 2, true)
_ = insertPublicDashboard(t, pubdashStore, dashboard2.UID, dashboard2.OrgID, true)
pubdashes, err := pubdashStore.FindByDashboardFolder(context.Background(), dashboard)
require.NoError(t, err)
assert.Len(t, pubdashes, 1)
assert.Equal(t, pubdash, pubdashes[0])
})
}
// helper function to insert a dashboard // helper function to insert a dashboard
func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64, func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64,
folderId int64, isFolder bool, tags ...interface{}) *dashboards.Dashboard { folderId int64, isFolder bool, tags ...interface{}) *dashboards.Dashboard {

View File

@ -1,4 +1,4 @@
// Code generated by mockery v2.14.0. DO NOT EDIT. // Code generated by mockery v2.16.0. DO NOT EDIT.
package publicdashboards package publicdashboards
@ -46,13 +46,27 @@ func (_m *FakePublicDashboardService) Create(ctx context.Context, u *user.Signed
return r0, r1 return r0, r1
} }
// Delete provides a mock function with given fields: ctx, orgId, uid // Delete provides a mock function with given fields: ctx, uid
func (_m *FakePublicDashboardService) Delete(ctx context.Context, orgId int64, uid string) error { func (_m *FakePublicDashboardService) Delete(ctx context.Context, uid string) error {
ret := _m.Called(ctx, orgId, uid) ret := _m.Called(ctx, uid)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, orgId, uid) r0 = rf(ctx, uid)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteByDashboard provides a mock function with given fields: ctx, dashboard
func (_m *FakePublicDashboardService) DeleteByDashboard(ctx context.Context, dashboard *dashboards.Dashboard) error {
ret := _m.Called(ctx, dashboard)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *dashboards.Dashboard) error); ok {
r0 = rf(ctx, dashboard)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
} }

View File

@ -1,4 +1,4 @@
// Code generated by mockery v2.14.0. DO NOT EDIT. // Code generated by mockery v2.16.0. DO NOT EDIT.
package publicdashboards package publicdashboards
@ -14,6 +14,20 @@ type FakePublicDashboardServiceWrapper struct {
mock.Mock mock.Mock
} }
// Delete provides a mock function with given fields: ctx, uid
func (_m *FakePublicDashboardServiceWrapper) Delete(ctx context.Context, uid string) error {
ret := _m.Called(ctx, uid)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, uid)
} else {
r0 = ret.Error(0)
}
return r0
}
// FindByDashboardUid provides a mock function with given fields: ctx, orgId, dashboardUid // FindByDashboardUid provides a mock function with given fields: ctx, orgId, dashboardUid
func (_m *FakePublicDashboardServiceWrapper) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { func (_m *FakePublicDashboardServiceWrapper) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
ret := _m.Called(ctx, orgId, dashboardUid) ret := _m.Called(ctx, orgId, dashboardUid)

View File

@ -1,4 +1,4 @@
// Code generated by mockery v2.14.0. DO NOT EDIT. // Code generated by mockery v2.16.0. DO NOT EDIT.
package publicdashboards package publicdashboards
@ -37,20 +37,20 @@ func (_m *FakePublicDashboardStore) Create(ctx context.Context, cmd models.SaveP
return r0, r1 return r0, r1
} }
// Delete provides a mock function with given fields: ctx, orgId, uid // Delete provides a mock function with given fields: ctx, uid
func (_m *FakePublicDashboardStore) Delete(ctx context.Context, orgId int64, uid string) (int64, error) { func (_m *FakePublicDashboardStore) Delete(ctx context.Context, uid string) (int64, error) {
ret := _m.Called(ctx, orgId, uid) ret := _m.Called(ctx, uid)
var r0 int64 var r0 int64
if rf, ok := ret.Get(0).(func(context.Context, int64, string) int64); ok { if rf, ok := ret.Get(0).(func(context.Context, string) int64); ok {
r0 = rf(ctx, orgId, uid) r0 = rf(ctx, uid)
} else { } else {
r0 = ret.Get(0).(int64) r0 = ret.Get(0).(int64)
} }
var r1 error var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok { if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, orgId, uid) r1 = rf(ctx, uid)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }
@ -169,6 +169,29 @@ func (_m *FakePublicDashboardStore) FindByAccessToken(ctx context.Context, acces
return r0, r1 return r0, r1
} }
// FindByDashboardFolder provides a mock function with given fields: ctx, dashboard
func (_m *FakePublicDashboardStore) FindByDashboardFolder(ctx context.Context, dashboard *dashboards.Dashboard) ([]*models.PublicDashboard, error) {
ret := _m.Called(ctx, dashboard)
var r0 []*models.PublicDashboard
if rf, ok := ret.Get(0).(func(context.Context, *dashboards.Dashboard) []*models.PublicDashboard); ok {
r0 = rf(ctx, dashboard)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.PublicDashboard)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *dashboards.Dashboard) error); ok {
r1 = rf(ctx, dashboard)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByDashboardUid provides a mock function with given fields: ctx, orgId, dashboardUid // FindByDashboardUid provides a mock function with given fields: ctx, orgId, dashboardUid
func (_m *FakePublicDashboardStore) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { func (_m *FakePublicDashboardStore) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
ret := _m.Called(ctx, orgId, dashboardUid) ret := _m.Called(ctx, orgId, dashboardUid)

View File

@ -24,7 +24,8 @@ type Service interface {
Find(ctx context.Context, uid string) (*PublicDashboard, error) Find(ctx context.Context, uid string) (*PublicDashboard, error)
Create(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) Create(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error)
Update(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) Update(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error)
Delete(ctx context.Context, orgId int64, uid string) error Delete(ctx context.Context, uid string) error
DeleteByDashboard(ctx context.Context, dashboard *dashboards.Dashboard) error
GetMetricRequest(ctx context.Context, dashboard *dashboards.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error) GetMetricRequest(ctx context.Context, dashboard *dashboards.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error)
GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error) GetQueryDataResponse(ctx context.Context, skipCache bool, reqDTO PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error)
@ -41,6 +42,7 @@ type Service interface {
//go:generate mockery --name ServiceWrapper --structname FakePublicDashboardServiceWrapper --inpackage --filename public_dashboard_service_wrapper_mock.go //go:generate mockery --name ServiceWrapper --structname FakePublicDashboardServiceWrapper --inpackage --filename public_dashboard_service_wrapper_mock.go
type ServiceWrapper interface { type ServiceWrapper interface {
FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error)
Delete(ctx context.Context, uid string) error
} }
//go:generate mockery --name Store --structname FakePublicDashboardStore --inpackage --filename public_dashboard_store_mock.go //go:generate mockery --name Store --structname FakePublicDashboardStore --inpackage --filename public_dashboard_store_mock.go
@ -52,9 +54,10 @@ type Store interface {
FindAll(ctx context.Context, orgId int64) ([]PublicDashboardListResponse, error) FindAll(ctx context.Context, orgId int64) ([]PublicDashboardListResponse, error)
Create(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error) Create(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error)
Update(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error) Update(ctx context.Context, cmd SavePublicDashboardCommand) (int64, error)
Delete(ctx context.Context, orgId int64, uid string) (int64, error) Delete(ctx context.Context, uid string) (int64, error)
GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error) GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error)
FindByDashboardFolder(ctx context.Context, dashboard *dashboards.Dashboard) ([]*PublicDashboard, error)
ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error) ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error)
ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error)
} }

View File

@ -329,19 +329,39 @@ func (pd *PublicDashboardServiceImpl) GetOrgIdByAccessToken(ctx context.Context,
return pd.store.GetOrgIdByAccessToken(ctx, accessToken) return pd.store.GetOrgIdByAccessToken(ctx, accessToken)
} }
func (pd *PublicDashboardServiceImpl) Delete(ctx context.Context, orgId int64, uid string) error { func (pd *PublicDashboardServiceImpl) Delete(ctx context.Context, uid string) error {
affectedRows, err := pd.store.Delete(ctx, orgId, uid) return pd.serviceWrapper.Delete(ctx, uid)
if err != nil {
return ErrInternalServerError.Errorf("Delete: failed to delete a public dashboard by orgId: %d and Uid: %s %w", orgId, uid, err)
} }
if affectedRows == 0 { func (pd *PublicDashboardServiceImpl) DeleteByDashboard(ctx context.Context, dashboard *dashboards.Dashboard) error {
return ErrPublicDashboardNotFound.Errorf("Delete: Public dashboard not found by orgId: %d and Uid: %s", orgId, uid) if dashboard.IsFolder {
// get all pubdashes for the folder
pubdashes, err := pd.store.FindByDashboardFolder(ctx, dashboard)
if err != nil {
return err
}
// delete each pubdash
for _, pubdash := range pubdashes {
err = pd.serviceWrapper.Delete(ctx, pubdash.Uid)
if err != nil {
return err
}
} }
return nil return nil
} }
pubdash, err := pd.store.FindByDashboardUid(ctx, dashboard.OrgID, dashboard.UID)
if err != nil {
return ErrInternalServerError.Errorf("DeleteByDashboard: error finding a public dashboard by dashboard orgId: %d and Uid: %s %w", dashboard.OrgID, dashboard.UID, err)
}
if pubdash == nil {
return nil
}
return pd.serviceWrapper.Delete(ctx, pubdash.Uid)
}
// intervalMS and maxQueryData values are being calculated on the frontend for regular dashboards // intervalMS and maxQueryData values are being calculated on the frontend for regular dashboards
// we are doing the same for public dashboards but because this access would be public, we need a way to keep this // we are doing the same for public dashboards but because this access would be public, we need a way to keep this
// values inside reasonable bounds to avoid an attack that could hit data sources with a small interval and a big // values inside reasonable bounds to avoid an attack that could hit data sources with a small interval and a big

View File

@ -498,13 +498,13 @@ func TestDeletePublicDashboard(t *testing.T) {
{ {
Name: "Public dashboard not found", Name: "Public dashboard not found",
AffectedRowsResp: 0, AffectedRowsResp: 0,
ExpectedErrResp: ErrPublicDashboardNotFound.Errorf("Delete: Public dashboard not found by orgId: 13 and Uid: uid"), ExpectedErrResp: nil,
StoreRespErr: nil, StoreRespErr: nil,
}, },
{ {
Name: "Database error", Name: "Database error",
AffectedRowsResp: 0, AffectedRowsResp: 0,
ExpectedErrResp: ErrInternalServerError.Errorf("Delete: failed to delete a public dashboard by orgId: 13 and Uid: uid db error!"), ExpectedErrResp: ErrInternalServerError.Errorf("Delete: failed to delete a public dashboard by Uid: uid db error!"),
StoreRespErr: errors.New("db error!"), StoreRespErr: errors.New("db error!"),
}, },
} }
@ -512,14 +512,18 @@ func TestDeletePublicDashboard(t *testing.T) {
for _, tt := range testCases { for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) { t.Run(tt.Name, func(t *testing.T) {
store := NewFakePublicDashboardStore(t) store := NewFakePublicDashboardStore(t)
store.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.AffectedRowsResp, tt.StoreRespErr) store.On("Delete", mock.Anything, mock.Anything).Return(tt.AffectedRowsResp, tt.StoreRespErr)
serviceWrapper := &PublicDashboardServiceWrapperImpl{
service := &PublicDashboardServiceImpl{
log: log.New("test.logger"), log: log.New("test.logger"),
store: store, store: store,
} }
service := &PublicDashboardServiceImpl{
log: log.New("test.logger"),
store: store,
serviceWrapper: serviceWrapper,
}
err := service.Delete(context.Background(), 13, "uid") err := service.Delete(context.Background(), "uid")
if tt.ExpectedErrResp != nil { if tt.ExpectedErrResp != nil {
assert.Equal(t, tt.ExpectedErrResp.Error(), err.Error()) assert.Equal(t, tt.ExpectedErrResp.Error(), err.Error())
assert.Equal(t, tt.ExpectedErrResp.Error(), err.Error()) assert.Equal(t, tt.ExpectedErrResp.Error(), err.Error())
@ -966,6 +970,56 @@ func TestPublicDashboardServiceImpl_NewPublicDashboardAccessToken(t *testing.T)
} }
} }
func TestDeleteByDashboard(t *testing.T) {
t.Run("will return nil when pubdash not found", func(t *testing.T) {
store := NewFakePublicDashboardStore(t)
pd := &PublicDashboardServiceImpl{store: store, serviceWrapper: ProvideServiceWrapper(store)}
dashboard := &dashboards.Dashboard{UID: "1", OrgID: 1, IsFolder: false}
store.On("FindByDashboardUid", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
err := pd.DeleteByDashboard(context.Background(), dashboard)
assert.Nil(t, err)
})
t.Run("will delete pubdash when dashboard deleted", func(t *testing.T) {
store := NewFakePublicDashboardStore(t)
pd := &PublicDashboardServiceImpl{store: store, serviceWrapper: ProvideServiceWrapper(store)}
dashboard := &dashboards.Dashboard{UID: "1", OrgID: 1, IsFolder: false}
pubdash := &PublicDashboard{Uid: "2", OrgId: 1, DashboardUid: dashboard.UID}
store.On("FindByDashboardUid", mock.Anything, mock.Anything, mock.Anything).Return(pubdash, nil)
store.On("Delete", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
err := pd.DeleteByDashboard(context.Background(), dashboard)
require.NoError(t, err)
})
t.Run("will delete pubdashes when dashboard folder deleted", func(t *testing.T) {
store := NewFakePublicDashboardStore(t)
pd := &PublicDashboardServiceImpl{store: store, serviceWrapper: ProvideServiceWrapper(store)}
dashboard := &dashboards.Dashboard{UID: "1", OrgID: 1, IsFolder: true}
pubdash1 := &PublicDashboard{Uid: "2", OrgId: 1, DashboardUid: dashboard.UID}
pubdash2 := &PublicDashboard{Uid: "3", OrgId: 1, DashboardUid: dashboard.UID}
store.On("FindByDashboardFolder", mock.Anything, mock.Anything).Return([]*PublicDashboard{pubdash1, pubdash2}, nil)
store.On("Delete", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
store.On("Delete", mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil)
err := pd.DeleteByDashboard(context.Background(), dashboard)
require.NoError(t, err)
})
}
func TestGenerateAccessToken(t *testing.T) {
accessToken, err := GenerateAccessToken()
t.Run("length", func(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, 32, len(accessToken))
})
t.Run("no - ", func(t *testing.T) {
assert.False(t, strings.Contains("-", accessToken))
})
}
func CreateDatasource(dsType string, uid string) struct { func CreateDatasource(dsType string, uid string) struct {
Type *string `json:"type,omitempty"` Type *string `json:"type,omitempty"`
Uid *string `json:"uid,omitempty"` Uid *string `json:"uid,omitempty"`
@ -1070,16 +1124,3 @@ func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title st
dash.Data.Set("uid", dash.UID) dash.Data.Set("uid", dash.UID)
return dash return dash
} }
func TestGenerateAccessToken(t *testing.T) {
accessToken, err := GenerateAccessToken()
t.Run("length", func(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, 32, len(accessToken))
})
t.Run("no - ", func(t *testing.T) {
assert.False(t, strings.Contains("-", accessToken))
})
}

View File

@ -43,3 +43,12 @@ func (pd *PublicDashboardServiceWrapperImpl) FindByDashboardUid(ctx context.Cont
return pubdash, nil return pubdash, nil
} }
func (pd *PublicDashboardServiceWrapperImpl) Delete(ctx context.Context, uid string) error {
_, err := pd.store.Delete(ctx, uid)
if err != nil {
return ErrInternalServerError.Errorf("Delete: failed to delete a public dashboard by Uid: %s %w", uid, err)
}
return nil
}