LibraryPanels: removes feature toggle (#33839)

* WIP: intial structure

* Refactor: adds create library element endpoint

* Feature: adds delete library element

* wip

* Refactor: adds get api

* Refactor: adds get all api

* Refactor: adds patch api

* Refactor: changes to library_element_connection

* Refactor: add get connections api

* wip: in the middle of refactor

* wip

* Refactor: consolidating both api:s

* Refactor: points front end to library elements api

* Tests: Fixes broken test

* LibraryPanels: removes feature toggle

* Fix: fixes delete library elements in folder and adds tests

* Tests: fixes snapshot

* Refactor: adds service interfaces so they can be easily mocked

* Refactor: changes order of tabs in manage folder

* Refactor: fixes so link does not cover whole card

* Refactor: fixes index string name

* Update pkg/services/libraryelements/libraryelements.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/libraryelements/libraryelements_permissions_test.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/libraryelements/database.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Chore: changes after PR comments

* Update libraryelements.go

* Update libraryelements.go

* Chore: updates after PR comments

* Chore: trying to fix build error

* Refactor: fixed stupid mistake

* Update libraryelements.go

* Chore: tries to fix build errors

* Refactor: trying to fix MySQL key length

* Update libraryelements.go

* Update pkg/services/libraryelements/libraryelements.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/librarypanels/librarypanels.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Refactor: changes after PR comments

* Refactor: changes after PR comments

* Tests: fixes tests

* Refactor: renames connections to connectedDashboards

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Hugo Häggmark
2021-05-12 08:48:17 +02:00
committed by GitHub
co-authored by Arve Knudsen
parent f61328af2d
commit 69d9f427e1
25 changed files with 477 additions and 441 deletions
+18 -25
View File
@@ -170,13 +170,12 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
// make sure db version is in sync with json model version
dash.Data.Set("version", dash.Version)
if hs.Cfg.IsPanelLibraryEnabled() {
// load library panels JSON for this dashboard
err = hs.LibraryPanelService.LoadLibraryPanelsForDashboard(c, dash)
if err != nil {
return response.Error(500, "Error while loading library panels", err)
}
// load library panels JSON for this dashboard
err = hs.LibraryPanelService.LoadLibraryPanelsForDashboard(c, dash)
if err != nil {
return response.Error(500, "Error while loading library panels", err)
}
var trimedJson simplejson.Json
if trimDefaults && !hs.LoadSchemaService.IsDisabled() {
trimedJson, err = hs.LoadSchemaService.DashboardTrimDefaults(*dash.Data)
@@ -249,16 +248,14 @@ func (hs *HTTPServer) deleteDashboard(c *models.ReqContext) response.Response {
return dashboardGuardianResponse(err)
}
if hs.Cfg.IsPanelLibraryEnabled() {
// disconnect all library elements for this dashboard
err := hs.LibraryElementService.DisconnectElementsFromDashboard(c, dash.Id)
if err != nil {
hs.log.Error("Failed to disconnect library elements", "dashboard", dash.Id, "user", c.SignedInUser.UserId, "error", err)
}
// disconnect all library elements for this dashboard
err := hs.LibraryElementService.DisconnectElementsFromDashboard(c, dash.Id)
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(dash.Id, c.OrgId)
err = svc.DeleteDashboard(dash.Id, c.OrgId)
if err != nil {
var dashboardErr models.DashboardErr
if ok := errors.As(err, &dashboardErr); ok {
@@ -318,12 +315,10 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboa
allowUiUpdate = hs.ProvisioningService.GetAllowUIUpdatesFromConfig(provisioningData.Name)
}
if hs.Cfg.IsPanelLibraryEnabled() {
// clean up all unnecessary library panels JSON properties so we store a minimum JSON
err = hs.LibraryPanelService.CleanLibraryPanelsForDashboard(dash)
if err != nil {
return response.Error(500, "Error while cleaning library panels", err)
}
// clean up all unnecessary library panels JSON properties so we store a minimum JSON
err = hs.LibraryPanelService.CleanLibraryPanelsForDashboard(dash)
if err != nil {
return response.Error(500, "Error while cleaning library panels", err)
}
dashItem := &dashboards.SaveDashboardDTO{
@@ -373,12 +368,10 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboa
}
}
if hs.Cfg.IsPanelLibraryEnabled() {
// connect library panels for this dashboard after the dashboard is stored and has an ID
err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(c, dashboard)
if err != nil {
return response.Error(500, "Error while connecting library panels", err)
}
// connect library panels for this dashboard after the dashboard is stored and has an ID
err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(c, dashboard)
if err != nil {
return response.Error(500, "Error while connecting library panels", err)
}
c.TimeRequest(metrics.MApiDashboardSave)
+99 -18
View File
@@ -16,6 +16,7 @@ import (
"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/libraryelements"
"github.com/grafana/grafana/pkg/services/live"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/quota"
@@ -172,7 +173,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUp()
callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardBySlug(sc, &HTTPServer{
Cfg: setting.NewCfg(),
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
})
assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
@@ -182,7 +187,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUp()
callDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardBySlug(sc, &HTTPServer{
Cfg: setting.NewCfg(),
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
})
assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
@@ -237,7 +246,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
state := setUp()
callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardBySlug(sc, &HTTPServer{
Cfg: setting.NewCfg(),
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
})
assert.Equal(t, 200, sc.resp.Code)
assert.Equal(t, "child-dash", state.dashQueries[0].Slug)
})
@@ -246,7 +259,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
state := setUp()
callDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardBySlug(sc, &HTTPServer{
Cfg: setting.NewCfg(),
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
})
assert.Equal(t, 200, sc.resp.Code)
assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid)
})
@@ -271,8 +288,10 @@ func TestDashboardAPIEndpoint(t *testing.T) {
t.Run("Given a dashboard with a parent folder which has an ACL", func(t *testing.T) {
hs := &HTTPServer{
Cfg: setting.NewCfg(),
Live: newTestLive(t),
Cfg: setting.NewCfg(),
Live: newTestLive(t),
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
}
setUp := func() *testState {
@@ -1031,7 +1050,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
"/api/dashboards/db/:slug", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
callDeleteDashboardBySlug(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardBySlug(sc, &HTTPServer{
Cfg: setting.NewCfg(),
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
})
assert.Equal(t, 400, sc.resp.Code)
result := sc.ToJSON()
@@ -1041,7 +1064,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/abcdefghi", "/api/dashboards/db/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
callDeleteDashboardByUID(sc, &HTTPServer{Cfg: setting.NewCfg()})
callDeleteDashboardBySlug(sc, &HTTPServer{
Cfg: setting.NewCfg(),
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
})
assert.Equal(t, 400, sc.resp.Code)
result := sc.ToJSON()
@@ -1073,8 +1100,10 @@ func TestDashboardAPIEndpoint(t *testing.T) {
}
hs := &HTTPServer{
Cfg: setting.NewCfg(),
ProvisioningService: mock,
Cfg: setting.NewCfg(),
ProvisioningService: mock,
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
}
callGetDashboard(sc, hs)
@@ -1095,10 +1124,16 @@ func getDashboardShouldReturn200WithConfig(sc *scenarioContext, provisioningServ
provisioningService = provisioning.NewProvisioningServiceMock()
}
libraryPanelsService := mockLibraryPanelService{}
libraryElementsService := mockLibraryElementService{}
hs := &HTTPServer{
Cfg: setting.NewCfg(),
ProvisioningService: provisioningService,
Cfg: setting.NewCfg(),
LibraryPanelService: &libraryPanelsService,
LibraryElementService: &libraryElementsService,
ProvisioningService: provisioningService,
}
callGetDashboard(sc, hs)
require.Equal(sc.t, 200, sc.resp.Code)
@@ -1185,7 +1220,9 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
QuotaService: &quota.QuotaService{
Cfg: cfg,
},
PluginManager: &fakePluginManager{},
PluginManager: &fakePluginManager{},
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
}
sc := setupScenarioContext(t, url)
@@ -1242,11 +1279,13 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
cfg := setting.NewCfg()
hs := HTTPServer{
Cfg: cfg,
Bus: bus.GetBus(),
ProvisioningService: provisioning.NewProvisioningServiceMock(),
Live: newTestLive(t),
QuotaService: &quota.QuotaService{Cfg: cfg},
Cfg: cfg,
Bus: bus.GetBus(),
ProvisioningService: provisioning.NewProvisioningServiceMock(),
Live: newTestLive(t),
QuotaService: &quota.QuotaService{Cfg: cfg},
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
}
sc := setupScenarioContext(t, url)
@@ -1293,3 +1332,45 @@ func (s mockDashboardProvisioningService) GetProvisionedDashboardDataByDashboard
*models.DashboardProvisioning, error) {
return nil, nil
}
type mockLibraryPanelService struct {
}
func (m *mockLibraryPanelService) LoadLibraryPanelsForDashboard(c *models.ReqContext, dash *models.Dashboard) error {
return nil
}
func (m *mockLibraryPanelService) CleanLibraryPanelsForDashboard(dash *models.Dashboard) error {
return nil
}
func (m *mockLibraryPanelService) ConnectLibraryPanelsForDashboard(c *models.ReqContext, dash *models.Dashboard) error {
return nil
}
type mockLibraryElementService struct {
}
func (l *mockLibraryElementService) CreateElement(c *models.ReqContext, cmd libraryelements.CreateLibraryElementCommand) (libraryelements.LibraryElementDTO, error) {
return libraryelements.LibraryElementDTO{}, nil
}
// GetElementsForDashboard gets all connected elements for a specific dashboard.
func (l *mockLibraryElementService) GetElementsForDashboard(c *models.ReqContext, dashboardID int64) (map[string]libraryelements.LibraryElementDTO, error) {
return map[string]libraryelements.LibraryElementDTO{}, nil
}
// ConnectElementsToDashboard connects elements to a specific dashboard.
func (l *mockLibraryElementService) ConnectElementsToDashboard(c *models.ReqContext, elementUIDs []string, dashboardID int64) error {
return nil
}
// DisconnectElementsFromDashboard disconnects elements from a specific dashboard.
func (l *mockLibraryElementService) DisconnectElementsFromDashboard(c *models.ReqContext, dashboardID int64) error {
return nil
}
// DeleteLibraryElementsInFolder deletes all elements for a specific folder.
func (l *mockLibraryElementService) DeleteLibraryElementsInFolder(c *models.ReqContext, folderUID string) error {
return nil
}
+6 -9
View File
@@ -4,13 +4,12 @@ import (
"errors"
"fmt"
"github.com/grafana/grafana/pkg/services/libraryelements"
"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"
)
@@ -88,14 +87,12 @@ func (hs *HTTPServer) UpdateFolder(c *models.ReqContext, cmd models.UpdateFolder
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)
if hs.Cfg.IsPanelLibraryEnabled() {
err := hs.LibraryElementService.DeleteLibraryElementsInFolder(c, c.Params(":uid"))
if err != nil {
if errors.Is(err, libraryelements.ErrFolderHasConnectedLibraryElements) {
return response.Error(403, "Folder could not be deleted because it contains library elements in use", err)
}
return ToFolderErrorResponse(err)
err := hs.LibraryElementService.DeleteLibraryElementsInFolder(c, c.Params(":uid"))
if err != nil {
if errors.Is(err, libraryelements.ErrFolderHasConnectedLibraryElements) {
return response.Error(403, "Folder could not be deleted because it contains library elements in use", err)
}
return ToFolderErrorResponse(err)
}
f, err := s.DeleteFolder(c.Params(":uid"))
+5 -4
View File
@@ -13,6 +13,9 @@ import (
"strings"
"sync"
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/librarypanels"
"github.com/grafana/grafana/pkg/api/routing"
httpstatic "github.com/grafana/grafana/pkg/api/static"
"github.com/grafana/grafana/pkg/bus"
@@ -34,8 +37,6 @@ import (
"github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/hooks"
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/librarypanels"
"github.com/grafana/grafana/pkg/services/live"
"github.com/grafana/grafana/pkg/services/live/pushhttp"
"github.com/grafana/grafana/pkg/services/login"
@@ -97,13 +98,13 @@ type HTTPServer struct {
LivePushGateway *pushhttp.Gateway `inject:""`
ContextHandler *contexthandler.ContextHandler `inject:""`
SQLStore *sqlstore.SQLStore `inject:""`
LibraryPanelService *librarypanels.LibraryPanelService `inject:""`
LibraryElementService *libraryelements.LibraryElementService `inject:""`
DataService *tsdb.Service `inject:""`
PluginDashboardService *plugindashboards.Service `inject:""`
AlertEngine *alerting.AlertEngine `inject:""`
LoadSchemaService *schemaloader.SchemaLoaderService `inject:""`
Alertmanager *notifier.Alertmanager `inject:""`
LibraryPanelService librarypanels.Service `inject:""`
LibraryElementService libraryelements.Service `inject:""`
Listener net.Listener
}
+6 -8
View File
@@ -168,14 +168,12 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
Icon: "camera",
})
if hs.Cfg.IsPanelLibraryEnabled() {
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "Library panels",
Id: "library-panels",
Url: hs.Cfg.AppSubURL + "/library-panels",
Icon: "library-panel",
})
}
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{
Text: "Library panels",
Id: "library-panels",
Url: hs.Cfg.AppSubURL + "/library-panels",
Icon: "library-panel",
})
}
navTree = append(navTree, &dtos.NavLink{