Commit d6449c16 by Hugo Häggmark Committed by GitHub

LibraryPanels: adds connections (#30212)

* LibraryPanels: adds connections

* Chore: testing signing verification

* Update pkg/services/librarypanels/librarypanels_test.go

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

* Update pkg/services/librarypanels/librarypanels_test.go

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

* Update pkg/services/librarypanels/librarypanels_test.go

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

* Update pkg/services/librarypanels/librarypanels_test.go

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

* Update pkg/services/librarypanels/librarypanels_test.go

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

* Update pkg/services/librarypanels/librarypanels_test.go

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

* Update pkg/services/librarypanels/librarypanels_test.go

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

* Chore: changes after PR comments

* Chore: changes after PR comments

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
parent 4420a959
......@@ -18,9 +18,12 @@ func (lps *LibraryPanelService) registerAPIEndpoints() {
lps.RouteRegister.Group("/api/library-panels", func(libraryPanels routing.RouteRegister) {
libraryPanels.Post("/", middleware.ReqSignedIn, binding.Bind(createLibraryPanelCommand{}), api.Wrap(lps.createHandler))
libraryPanels.Post("/:uid/dashboards/:dashboardId", middleware.ReqSignedIn, api.Wrap(lps.connectHandler))
libraryPanels.Delete("/:uid", middleware.ReqSignedIn, api.Wrap(lps.deleteHandler))
libraryPanels.Delete("/:uid/dashboards/:dashboardId", middleware.ReqSignedIn, api.Wrap(lps.disconnectHandler))
libraryPanels.Get("/", middleware.ReqSignedIn, api.Wrap(lps.getAllHandler))
libraryPanels.Get("/:uid", middleware.ReqSignedIn, api.Wrap(lps.getHandler))
libraryPanels.Get("/:uid/dashboards/", middleware.ReqSignedIn, api.Wrap(lps.getConnectedDashboardsHandler))
libraryPanels.Patch("/:uid", middleware.ReqSignedIn, binding.Bind(patchLibraryPanelCommand{}), api.Wrap(lps.patchHandler))
})
}
......@@ -38,7 +41,19 @@ func (lps *LibraryPanelService) createHandler(c *models.ReqContext, cmd createLi
return api.JSON(200, util.DynMap{"result": panel})
}
// deleteHandler handles DELETE /api/library-panels/:uid
// connectHandler handles POST /api/library-panels/:uid/dashboards/:dashboardId.
func (lps *LibraryPanelService) connectHandler(c *models.ReqContext) api.Response {
if err := lps.connectDashboard(c, c.Params(":uid"), c.ParamsInt64(":dashboardId")); err != nil {
if errors.Is(err, errLibraryPanelNotFound) {
return api.Error(404, errLibraryPanelNotFound.Error(), err)
}
return api.Error(500, "Failed to connect library panel", err)
}
return api.Success("Library panel connected")
}
// deleteHandler handles DELETE /api/library-panels/:uid.
func (lps *LibraryPanelService) deleteHandler(c *models.ReqContext) api.Response {
err := lps.deleteLibraryPanel(c, c.Params(":uid"))
if err != nil {
......@@ -51,7 +66,23 @@ func (lps *LibraryPanelService) deleteHandler(c *models.ReqContext) api.Response
return api.Success("Library panel deleted")
}
// getHandler handles GET /api/library-panels/:uid
// disconnectHandler handles DELETE /api/library-panels/:uid/dashboards/:dashboardId.
func (lps *LibraryPanelService) disconnectHandler(c *models.ReqContext) api.Response {
err := lps.disconnectDashboard(c, c.Params(":uid"), c.ParamsInt64(":dashboardId"))
if err != nil {
if errors.Is(err, errLibraryPanelNotFound) {
return api.Error(404, errLibraryPanelNotFound.Error(), err)
}
if errors.Is(err, errLibraryPanelDashboardNotFound) {
return api.Error(404, errLibraryPanelDashboardNotFound.Error(), err)
}
return api.Error(500, "Failed to disconnect library panel", err)
}
return api.Success("Library panel disconnected")
}
// getHandler handles GET /api/library-panels/:uid.
func (lps *LibraryPanelService) getHandler(c *models.ReqContext) api.Response {
libraryPanel, err := lps.getLibraryPanel(c, c.Params(":uid"))
if err != nil {
......@@ -64,7 +95,7 @@ func (lps *LibraryPanelService) getHandler(c *models.ReqContext) api.Response {
return api.JSON(200, util.DynMap{"result": libraryPanel})
}
// getAllHandler handles GET /api/library-panels/
// getAllHandler handles GET /api/library-panels/.
func (lps *LibraryPanelService) getAllHandler(c *models.ReqContext) api.Response {
libraryPanels, err := lps.getAllLibraryPanels(c)
if err != nil {
......@@ -74,6 +105,19 @@ func (lps *LibraryPanelService) getAllHandler(c *models.ReqContext) api.Response
return api.JSON(200, util.DynMap{"result": libraryPanels})
}
// getConnectedDashboardsHandler handles GET /api/library-panels/:uid/dashboards/.
func (lps *LibraryPanelService) getConnectedDashboardsHandler(c *models.ReqContext) api.Response {
dashboardIDs, err := lps.getConnectedDashboards(c, c.Params(":uid"))
if err != nil {
if errors.Is(err, errLibraryPanelNotFound) {
return api.Error(404, errLibraryPanelNotFound.Error(), err)
}
return api.Error(500, "Failed to get connected dashboards", err)
}
return api.JSON(200, util.DynMap{"result": dashboardIDs})
}
// patchHandler handles PATCH /api/library-panels/:uid
func (lps *LibraryPanelService) patchHandler(c *models.ReqContext, cmd patchLibraryPanelCommand) api.Response {
libraryPanel, err := lps.patchLibraryPanel(c, cmd, c.Params(":uid"))
......
......@@ -40,6 +40,34 @@ func (lps *LibraryPanelService) createLibraryPanel(c *models.ReqContext, cmd cre
return libraryPanel, err
}
// connectDashboard adds a connection between a Library Panel and a Dashboard.
func (lps *LibraryPanelService) connectDashboard(c *models.ReqContext, uid string, dashboardID int64) error {
err := lps.SQLStore.WithTransactionalDbSession(context.Background(), func(session *sqlstore.DBSession) error {
panel, err := getLibraryPanel(session, uid, c.SignedInUser.OrgId)
if err != nil {
return err
}
// TODO add check that dashboard exists
libraryPanelDashboard := libraryPanelDashboard{
DashboardID: dashboardID,
LibraryPanelID: panel.ID,
Created: time.Now(),
CreatedBy: c.SignedInUser.UserId,
}
if _, err := session.Insert(&libraryPanelDashboard); err != nil {
if lps.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
return nil
}
return err
}
return nil
})
return err
}
// deleteLibraryPanel deletes a Library Panel.
func (lps *LibraryPanelService) deleteLibraryPanel(c *models.ReqContext, uid string) error {
orgID := c.SignedInUser.OrgId
......@@ -59,6 +87,29 @@ func (lps *LibraryPanelService) deleteLibraryPanel(c *models.ReqContext, uid str
})
}
// disconnectDashboard deletes a connection between a Library Panel and a Dashboard.
func (lps *LibraryPanelService) disconnectDashboard(c *models.ReqContext, uid string, dashboardID int64) error {
return lps.SQLStore.WithTransactionalDbSession(context.Background(), func(session *sqlstore.DBSession) error {
panel, err := getLibraryPanel(session, uid, c.SignedInUser.OrgId)
if err != nil {
return err
}
result, err := session.Exec("DELETE FROM library_panel_dashboard WHERE librarypanel_id=? and dashboard_id=?", panel.ID, dashboardID)
if err != nil {
return err
}
if rowsAffected, err := result.RowsAffected(); err != nil {
return err
} else if rowsAffected != 1 {
return errLibraryPanelDashboardNotFound
}
return nil
})
}
func getLibraryPanel(session *sqlstore.DBSession, uid string, orgID int64) (LibraryPanel, error) {
libraryPanels := make([]LibraryPanel, 0)
session.Table("library_panel")
......@@ -105,6 +156,33 @@ func (lps *LibraryPanelService) getAllLibraryPanels(c *models.ReqContext) ([]Lib
return libraryPanels, err
}
// getConnectedDashboards gets all dashboards connected to a Library Panel.
func (lps *LibraryPanelService) getConnectedDashboards(c *models.ReqContext, uid string) ([]int64, error) {
connectedDashboardIDs := make([]int64, 0)
err := lps.SQLStore.WithDbSession(context.Background(), func(session *sqlstore.DBSession) error {
panel, err := getLibraryPanel(session, uid, c.SignedInUser.OrgId)
if err != nil {
return err
}
var libraryPanelDashboards []libraryPanelDashboard
session.Table("library_panel_dashboard")
session.Where("librarypanel_id=?", panel.ID)
err = session.Find(&libraryPanelDashboards)
if err != nil {
return err
}
for _, lpd := range libraryPanelDashboards {
connectedDashboardIDs = append(connectedDashboardIDs, lpd.DashboardID)
}
return nil
})
return connectedDashboardIDs, err
}
// patchLibraryPanel updates a Library Panel.
func (lps *LibraryPanelService) patchLibraryPanel(c *models.ReqContext, cmd patchLibraryPanelCommand, uid string) (LibraryPanel, error) {
var libraryPanel LibraryPanel
......
......@@ -67,4 +67,21 @@ func (lps *LibraryPanelService) AddMigration(mg *migrator.Migrator) {
mg.AddMigration("create library_panel table v1", migrator.NewAddTableMigration(libraryPanelV1))
mg.AddMigration("add index library_panel org_id & folder_id & name", migrator.NewAddIndexMigration(libraryPanelV1, libraryPanelV1.Indices[0]))
libraryPanelDashboardV1 := migrator.Table{
Name: "library_panel_dashboard",
Columns: []*migrator.Column{
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "librarypanel_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "dashboard_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
{Name: "created_by", Type: migrator.DB_BigInt, Nullable: false},
},
Indices: []*migrator.Index{
{Cols: []string{"librarypanel_id", "dashboard_id"}, Type: migrator.UniqueIndex},
},
}
mg.AddMigration("create library_panel_dashboard table v1", migrator.NewAddTableMigration(libraryPanelDashboardV1))
mg.AddMigration("add index library_panel_dashboard librarypanel_id & dashboard_id", migrator.NewAddIndexMigration(libraryPanelDashboardV1, libraryPanelDashboardV1.Indices[0]))
}
......@@ -27,6 +27,33 @@ func TestCreateLibraryPanel(t *testing.T) {
})
}
func TestConnectLibraryPanel(t *testing.T) {
testScenario(t, "When an admin tries to create a connection for a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", "dashboardId": "1"})
response := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 404, response.Status())
})
testScenario(t, "When an admin tries to create a connection that already exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var result libraryPanelResult
err := json.Unmarshal(response.Body(), &result)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, "dashboardId": "1"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
})
}
func TestDeleteLibraryPanel(t *testing.T) {
testScenario(t, "When an admin tries to delete a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
......@@ -67,6 +94,47 @@ func TestDeleteLibraryPanel(t *testing.T) {
})
}
func TestDisconnectLibraryPanel(t *testing.T) {
testScenario(t, "When an admin tries to remove a connection with a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", "dashboardId": "1"})
response := sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 404, response.Status())
})
testScenario(t, "When an admin tries to remove a connection that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var result libraryPanelResult
err := json.Unmarshal(response.Body(), &result)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, "dashboardId": "1"})
response = sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 404, response.Status())
})
testScenario(t, "When an admin tries to remove a connection that does exist, it should succeed",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var result libraryPanelResult
err := json.Unmarshal(response.Body(), &result)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, "dashboardId": "1"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
response = sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
})
}
func TestGetLibraryPanel(t *testing.T) {
testScenario(t, "When an admin tries to get a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
......@@ -178,6 +246,64 @@ func TestGetAllLibraryPanels(t *testing.T) {
})
}
func TestGetConnectedDashboards(t *testing.T) {
testScenario(t, "When an admin tries to get connected dashboards for a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown"})
response := sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 404, response.Status())
})
testScenario(t, "When an admin tries to get connected dashboards for a library panel that exists, but has no connections, it should return none",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var result libraryPanelResult
err := json.Unmarshal(response.Body(), &result)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
response = sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
var dashResult libraryPanelDashboardsResult
err = json.Unmarshal(response.Body(), &dashResult)
require.NoError(t, err)
require.Equal(t, 0, len(dashResult.Result))
})
testScenario(t, "When an admin tries to get connected dashboards for a library panel that exists and has connections, it should return connected dashboard IDs",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Text - Library Panel")
response := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, response.Status())
var result libraryPanelResult
err := json.Unmarshal(response.Body(), &result)
require.NoError(t, err)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": "11"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": "12"})
response = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
response = sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, response.Status())
var dashResult libraryPanelDashboardsResult
err = json.Unmarshal(response.Body(), &dashResult)
require.NoError(t, err)
require.Equal(t, 2, len(dashResult.Result))
require.Equal(t, int64(11), dashResult.Result[0])
require.Equal(t, int64(12), dashResult.Result[1])
})
}
func TestPatchLibraryPanel(t *testing.T) {
testScenario(t, "When an admin tries to patch a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
......@@ -411,6 +537,10 @@ type libraryPanelsResult struct {
Result []libraryPanel `json:"result"`
}
type libraryPanelDashboardsResult struct {
Result []int64 `json:"result"`
}
func overrideLibraryPanelServiceInRegistry(cfg *setting.Cfg) LibraryPanelService {
lps := LibraryPanelService{
SQLStore: nil,
......
......@@ -22,11 +22,24 @@ type LibraryPanel struct {
UpdatedBy int64
}
// libraryPanelDashboard is the model for library panel connections.
type libraryPanelDashboard struct {
ID int64 `xorm:"pk autoincr 'id'"`
LibraryPanelID int64 `xorm:"librarypanel_id"`
DashboardID int64 `xorm:"dashboard_id"`
Created time.Time
CreatedBy int64
}
var (
// errLibraryPanelAlreadyExists is an error for when the user tries to add a library panel that already exists.
errLibraryPanelAlreadyExists = errors.New("library panel with that name already exists")
// errLibraryPanelNotFound is an error for when a library panel can't be found.
errLibraryPanelNotFound = errors.New("library panel could not be found")
// errLibraryPanelDashboardNotFound is an error for when a library panel connection can't be found.
errLibraryPanelDashboardNotFound = errors.New("library panel connection could not be found")
)
// Commands
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment