From 1a5a280c86616038284e27deddece51c8240f04a Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Wed, 8 Mar 2023 14:54:35 -0600 Subject: [PATCH] Pubdash: Email sharing handle dashboard deleted (#64247) dashboard service calls pubdash service when dashboard deleted --- pkg/api/dashboard.go | 6 ++ pkg/api/dashboard_test.go | 38 ++++----- pkg/services/dashboards/database/database.go | 2 - .../dashboards/database/database_test.go | 78 ------------------ pkg/services/publicdashboards/api/api.go | 2 +- .../publicdashboards/database/database.go | 22 +++++- .../database/database_test.go | 43 +++++++++- .../public_dashboard_service_mock.go | 26 ++++-- .../public_dashboard_service_wrapper_mock.go | 16 +++- .../public_dashboard_store_mock.go | 39 +++++++-- .../publicdashboards/publicdashboard.go | 7 +- .../publicdashboards/service/service.go | 36 +++++++-- .../publicdashboards/service/service_test.go | 79 ++++++++++++++----- .../service/service_wapper.go | 9 +++ 14 files changed, 251 insertions(+), 152 deletions(-) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index bd4f5d79a46..09a9344b122 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -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) } + // 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) if err != nil { var dashboardErr dashboards.DashboardErr diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index f43c59da28e..a53b25cbce5 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -9,6 +9,8 @@ import ( "os" "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/mock" "github.com/stretchr/testify/require" @@ -304,7 +306,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { setUp() sc.sqlStore = mockSQLStore - hs.callDeleteDashboardByUID(t, sc, dashboardService) + hs.callDeleteDashboardByUID(t, sc, dashboardService, nil) assert.Equal(t, 403, sc.resp.Code) }, mockSQLStore) @@ -342,7 +344,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) { setUp() - hs.callDeleteDashboardByUID(t, sc, dashboardService) + hs.callDeleteDashboardByUID(t, sc, dashboardService, nil) assert.Equal(t, 403, sc.resp.Code) }, mockSQLStore) @@ -400,23 +402,9 @@ func TestDashboardAPIEndpoint(t *testing.T) { 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) - hs.callDeleteDashboardByUID(t, sc, dashboardService) - - assert.Equal(t, 200, sc.resp.Code) - }, 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) + 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) }, 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) { setUpInner() - hs.callDeleteDashboardByUID(t, sc, dashboardService) + hs.callDeleteDashboardByUID(t, sc, dashboardService, nil) assert.Equal(t, 403, sc.resp.Code) }, mockSQLStore) }) @@ -495,7 +483,9 @@ func TestDashboardAPIEndpoint(t *testing.T) { qResult := dashboards.NewDashboard("test") 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) - 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) }, 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) { setUpInner() - hs.callDeleteDashboardByUID(t, sc, dashboardService) + hs.callDeleteDashboardByUID(t, sc, dashboardService, nil) assert.Equal(t, 403, sc.resp.Code) }, mockSQLStore) @@ -1035,8 +1025,10 @@ func (hs *HTTPServer) callGetDashboardVersions(sc *scenarioContext) { } func (hs *HTTPServer) callDeleteDashboardByUID(t *testing.T, - sc *scenarioContext, mockDashboard *dashboards.FakeDashboardService) { + sc *scenarioContext, mockDashboard *dashboards.FakeDashboardService, mockPubdashService *publicdashboards.FakePublicDashboardService) { hs.DashboardService = mockDashboard + pubdashApi := api.ProvideApi(mockPubdashService, nil, nil, featuremgmt.WithFeatures()) + hs.PublicDashboardsApi = pubdashApi sc.handlerFunc = hs.DeleteDashboardByUID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() } diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 657ebc34e45..4126fe1e8b0 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -702,7 +702,6 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand, deletes := []string{ "DELETE FROM dashboard_tag 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 playlist_item WHERE type = 'dashboard_by_id' AND value = ?", "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 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_public WHERE dashboard_uid IN (SELECT uid FROM dashboard WHERE org_id = ? AND folder_id = ?)", } for _, sql := range childrenDeletes { _, err := sess.Exec(sql, dashboard.OrgID, dashboard.ID) diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index 05ee1636359..3f36a7c8f39 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -16,8 +16,6 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/dashboards" "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/search/model" "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/user" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" ) func TestIntegrationDashboardDataAccess(t *testing.T) { @@ -39,7 +36,6 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { var savedFolder, savedDash, savedDash2 *dashboards.Dashboard var dashboardStore dashboards.Store var starService star.Service - var publicDashboardStore *database.PublicDashboardStoreImpl setup := func() { 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") savedDash2 = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod") insertTestRule(t, sqlStore, savedFolder.OrgID, savedFolder.UID) - - publicDashboardStore = database.ProvideStore(sqlStore) } 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)) }) - 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) { setup() deleteCmd := &dashboards.DeleteDashboardCommand{ID: savedFolder.ID, ForceDeleteFolderRules: true} diff --git a/pkg/services/publicdashboards/api/api.go b/pkg/services/publicdashboards/api/api.go index 8ac11743ed6..921876e1949 100644 --- a/pkg/services/publicdashboards/api/api.go +++ b/pkg/services/publicdashboards/api/api.go @@ -196,7 +196,7 @@ func (api *Api) DeletePublicDashboard(c *contextmodel.ReqContext) response.Respo 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 { return response.Err(err) } diff --git a/pkg/services/publicdashboards/database/database.go b/pkg/services/publicdashboards/database/database.go index 180612ca12c..669f6c1d679 100644 --- a/pkg/services/publicdashboards/database/database.go +++ b/pkg/services/publicdashboards/database/database.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/publicdashboards" . "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 @@ -255,8 +256,8 @@ func (d *PublicDashboardStoreImpl) Update(ctx context.Context, cmd SavePublicDas } // Deletes a public dashboard -func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, orgId int64, uid string) (int64, error) { - dashboard := &PublicDashboard{OrgId: orgId, Uid: uid} +func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, uid string) (int64, error) { + dashboard := &PublicDashboard{Uid: uid} var affectedRows int64 err := d.sqlStore.WithDbSession(ctx, func(sess *db.Session) error { var err error @@ -267,3 +268,20 @@ func (d *PublicDashboardStoreImpl) Delete(ctx context.Context, orgId int64, uid 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 +} diff --git a/pkg/services/publicdashboards/database/database_test.go b/pkg/services/publicdashboards/database/database_test.go index 4dbb7eaff9c..e67da63d104 100644 --- a/pkg/services/publicdashboards/database/database_test.go +++ b/pkg/services/publicdashboards/database/database_test.go @@ -652,7 +652,7 @@ func TestIntegrationDelete(t *testing.T) { t.Run("Delete success", func(t *testing.T) { setup() // 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) 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) { setup() - affectedRows, err := publicdashboardStore.Delete(context.Background(), 15, "non-existent-uid") + affectedRows, err := publicdashboardStore.Delete(context.Background(), "non-existent-uid") require.NoError(t, err) 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 func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64, folderId int64, isFolder bool, tags ...interface{}) *dashboards.Dashboard { diff --git a/pkg/services/publicdashboards/public_dashboard_service_mock.go b/pkg/services/publicdashboards/public_dashboard_service_mock.go index 3fcf2bf2f87..a0bd21ac7e5 100644 --- a/pkg/services/publicdashboards/public_dashboard_service_mock.go +++ b/pkg/services/publicdashboards/public_dashboard_service_mock.go @@ -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 @@ -46,13 +46,27 @@ func (_m *FakePublicDashboardService) Create(ctx context.Context, u *user.Signed return r0, r1 } -// Delete provides a mock function with given fields: ctx, orgId, uid -func (_m *FakePublicDashboardService) Delete(ctx context.Context, orgId int64, uid string) error { - ret := _m.Called(ctx, orgId, uid) +// Delete provides a mock function with given fields: ctx, uid +func (_m *FakePublicDashboardService) Delete(ctx context.Context, uid string) error { + ret := _m.Called(ctx, uid) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { - r0 = rf(ctx, orgId, uid) + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + 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 { r0 = ret.Error(0) } diff --git a/pkg/services/publicdashboards/public_dashboard_service_wrapper_mock.go b/pkg/services/publicdashboards/public_dashboard_service_wrapper_mock.go index fa33d059e6a..5b96a227f48 100644 --- a/pkg/services/publicdashboards/public_dashboard_service_wrapper_mock.go +++ b/pkg/services/publicdashboards/public_dashboard_service_wrapper_mock.go @@ -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 @@ -14,6 +14,20 @@ type FakePublicDashboardServiceWrapper struct { 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 func (_m *FakePublicDashboardServiceWrapper) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { ret := _m.Called(ctx, orgId, dashboardUid) diff --git a/pkg/services/publicdashboards/public_dashboard_store_mock.go b/pkg/services/publicdashboards/public_dashboard_store_mock.go index 7e0bcdf3ae5..07e5f97cfdc 100644 --- a/pkg/services/publicdashboards/public_dashboard_store_mock.go +++ b/pkg/services/publicdashboards/public_dashboard_store_mock.go @@ -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 @@ -37,20 +37,20 @@ func (_m *FakePublicDashboardStore) Create(ctx context.Context, cmd models.SaveP return r0, r1 } -// Delete provides a mock function with given fields: ctx, orgId, uid -func (_m *FakePublicDashboardStore) Delete(ctx context.Context, orgId int64, uid string) (int64, error) { - ret := _m.Called(ctx, orgId, uid) +// Delete provides a mock function with given fields: ctx, uid +func (_m *FakePublicDashboardStore) Delete(ctx context.Context, uid string) (int64, error) { + ret := _m.Called(ctx, uid) var r0 int64 - if rf, ok := ret.Get(0).(func(context.Context, int64, string) int64); ok { - r0 = rf(ctx, orgId, uid) + if rf, ok := ret.Get(0).(func(context.Context, string) int64); ok { + r0 = rf(ctx, uid) } else { r0 = ret.Get(0).(int64) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok { - r1 = rf(ctx, orgId, uid) + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, uid) } else { r1 = ret.Error(1) } @@ -169,6 +169,29 @@ func (_m *FakePublicDashboardStore) FindByAccessToken(ctx context.Context, acces 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 func (_m *FakePublicDashboardStore) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { ret := _m.Called(ctx, orgId, dashboardUid) diff --git a/pkg/services/publicdashboards/publicdashboard.go b/pkg/services/publicdashboards/publicdashboard.go index 404dc5501cf..93bb2e96b61 100644 --- a/pkg/services/publicdashboards/publicdashboard.go +++ b/pkg/services/publicdashboards/publicdashboard.go @@ -24,7 +24,8 @@ type Service interface { Find(ctx context.Context, uid string) (*PublicDashboard, error) Create(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) 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 type ServiceWrapper interface { 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 @@ -52,9 +54,10 @@ type Store interface { FindAll(ctx context.Context, orgId int64) ([]PublicDashboardListResponse, error) Create(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) + FindByDashboardFolder(ctx context.Context, dashboard *dashboards.Dashboard) ([]*PublicDashboard, error) ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error) ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) } diff --git a/pkg/services/publicdashboards/service/service.go b/pkg/services/publicdashboards/service/service.go index a1c0a45b7a8..2c292f9e9f8 100644 --- a/pkg/services/publicdashboards/service/service.go +++ b/pkg/services/publicdashboards/service/service.go @@ -329,17 +329,37 @@ func (pd *PublicDashboardServiceImpl) GetOrgIdByAccessToken(ctx context.Context, return pd.store.GetOrgIdByAccessToken(ctx, accessToken) } -func (pd *PublicDashboardServiceImpl) Delete(ctx context.Context, orgId int64, uid string) error { - affectedRows, err := pd.store.Delete(ctx, orgId, uid) +func (pd *PublicDashboardServiceImpl) Delete(ctx context.Context, uid string) error { + return pd.serviceWrapper.Delete(ctx, uid) +} + +func (pd *PublicDashboardServiceImpl) DeleteByDashboard(ctx context.Context, dashboard *dashboards.Dashboard) error { + 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 + } + + pubdash, err := pd.store.FindByDashboardUid(ctx, dashboard.OrgID, dashboard.UID) if err != nil { - return ErrInternalServerError.Errorf("Delete: failed to delete a public dashboard by orgId: %d and Uid: %s %w", orgId, uid, err) + 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 } - if affectedRows == 0 { - return ErrPublicDashboardNotFound.Errorf("Delete: Public dashboard not found by orgId: %d and Uid: %s", orgId, uid) - } - - return nil + return pd.serviceWrapper.Delete(ctx, pubdash.Uid) } // intervalMS and maxQueryData values are being calculated on the frontend for regular dashboards diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index 560d8690927..6ae0ef9568f 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -498,13 +498,13 @@ func TestDeletePublicDashboard(t *testing.T) { { Name: "Public dashboard not found", AffectedRowsResp: 0, - ExpectedErrResp: ErrPublicDashboardNotFound.Errorf("Delete: Public dashboard not found by orgId: 13 and Uid: uid"), + ExpectedErrResp: nil, StoreRespErr: nil, }, { Name: "Database error", 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!"), }, } @@ -512,14 +512,18 @@ func TestDeletePublicDashboard(t *testing.T) { for _, tt := range testCases { t.Run(tt.Name, func(t *testing.T) { store := NewFakePublicDashboardStore(t) - store.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.AffectedRowsResp, tt.StoreRespErr) - - service := &PublicDashboardServiceImpl{ + store.On("Delete", mock.Anything, mock.Anything).Return(tt.AffectedRowsResp, tt.StoreRespErr) + serviceWrapper := &PublicDashboardServiceWrapperImpl{ log: log.New("test.logger"), 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 { 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 { Type *string `json:"type,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) 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)) - }) -} diff --git a/pkg/services/publicdashboards/service/service_wapper.go b/pkg/services/publicdashboards/service/service_wapper.go index c29919072c7..6dcb35ee82f 100644 --- a/pkg/services/publicdashboards/service/service_wapper.go +++ b/pkg/services/publicdashboards/service/service_wapper.go @@ -43,3 +43,12 @@ func (pd *PublicDashboardServiceWrapperImpl) FindByDashboardUid(ctx context.Cont 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 +}