mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 05:12:29 +08:00
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>
This commit is contained in:
@ -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
|
||||
|
Reference in New Issue
Block a user