mirror of
https://github.com/grafana/grafana.git
synced 2026-07-30 00:08:10 -05:00
LibraryPanels: Adds version column (#31590)
* Refactor: adds version column and fixes tests * Chore: adds version check when patching the library panel * Refactor: adds support for version in FrontEnd
This commit is contained in:
@@ -33,16 +33,7 @@ func (lps *LibraryPanelService) registerAPIEndpoints() {
|
||||
func (lps *LibraryPanelService) createHandler(c *models.ReqContext, cmd createLibraryPanelCommand) response.Response {
|
||||
panel, err := lps.createLibraryPanel(c, cmd)
|
||||
if err != nil {
|
||||
if errors.Is(err, errLibraryPanelAlreadyExists) {
|
||||
return response.Error(400, errLibraryPanelAlreadyExists.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderNotFound) {
|
||||
return response.Error(404, models.ErrFolderNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderAccessDenied) {
|
||||
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
|
||||
}
|
||||
return response.Error(500, "Failed to create library panel", err)
|
||||
return toLibraryPanelError(err, "Failed to create library panel")
|
||||
}
|
||||
|
||||
return response.JSON(200, util.DynMap{"result": panel})
|
||||
@@ -50,17 +41,9 @@ func (lps *LibraryPanelService) createHandler(c *models.ReqContext, cmd createLi
|
||||
|
||||
// connectHandler handles POST /api/library-panels/:uid/dashboards/:dashboardId.
|
||||
func (lps *LibraryPanelService) connectHandler(c *models.ReqContext) response.Response {
|
||||
if err := lps.connectDashboard(c, c.Params(":uid"), c.ParamsInt64(":dashboardId")); err != nil {
|
||||
if errors.Is(err, errLibraryPanelNotFound) {
|
||||
return response.Error(404, errLibraryPanelNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderNotFound) {
|
||||
return response.Error(404, models.ErrFolderNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderAccessDenied) {
|
||||
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
|
||||
}
|
||||
return response.Error(500, "Failed to connect library panel", err)
|
||||
err := lps.connectDashboard(c, c.Params(":uid"), c.ParamsInt64(":dashboardId"))
|
||||
if err != nil {
|
||||
return toLibraryPanelError(err, "Failed to connect library panel")
|
||||
}
|
||||
|
||||
return response.Success("Library panel connected")
|
||||
@@ -70,16 +53,7 @@ func (lps *LibraryPanelService) connectHandler(c *models.ReqContext) response.Re
|
||||
func (lps *LibraryPanelService) deleteHandler(c *models.ReqContext) response.Response {
|
||||
err := lps.deleteLibraryPanel(c, c.Params(":uid"))
|
||||
if err != nil {
|
||||
if errors.Is(err, errLibraryPanelNotFound) {
|
||||
return response.Error(404, errLibraryPanelNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderNotFound) {
|
||||
return response.Error(404, models.ErrFolderNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderAccessDenied) {
|
||||
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
|
||||
}
|
||||
return response.Error(500, "Failed to delete library panel", err)
|
||||
return toLibraryPanelError(err, "Failed to delete library panel")
|
||||
}
|
||||
|
||||
return response.Success("Library panel deleted")
|
||||
@@ -89,19 +63,7 @@ func (lps *LibraryPanelService) deleteHandler(c *models.ReqContext) response.Res
|
||||
func (lps *LibraryPanelService) disconnectHandler(c *models.ReqContext) response.Response {
|
||||
err := lps.disconnectDashboard(c, c.Params(":uid"), c.ParamsInt64(":dashboardId"))
|
||||
if err != nil {
|
||||
if errors.Is(err, errLibraryPanelNotFound) {
|
||||
return response.Error(404, errLibraryPanelNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, errLibraryPanelDashboardNotFound) {
|
||||
return response.Error(404, errLibraryPanelDashboardNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderNotFound) {
|
||||
return response.Error(404, models.ErrFolderNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderAccessDenied) {
|
||||
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
|
||||
}
|
||||
return response.Error(500, "Failed to disconnect library panel", err)
|
||||
return toLibraryPanelError(err, "Failed to disconnect library panel")
|
||||
}
|
||||
|
||||
return response.Success("Library panel disconnected")
|
||||
@@ -111,10 +73,7 @@ func (lps *LibraryPanelService) disconnectHandler(c *models.ReqContext) response
|
||||
func (lps *LibraryPanelService) getHandler(c *models.ReqContext) response.Response {
|
||||
libraryPanel, err := lps.getLibraryPanel(c, c.Params(":uid"))
|
||||
if err != nil {
|
||||
if errors.Is(err, errLibraryPanelNotFound) {
|
||||
return response.Error(404, errLibraryPanelNotFound.Error(), err)
|
||||
}
|
||||
return response.Error(500, "Failed to get library panel", err)
|
||||
return toLibraryPanelError(err, "Failed to get library panel")
|
||||
}
|
||||
|
||||
return response.JSON(200, util.DynMap{"result": libraryPanel})
|
||||
@@ -124,7 +83,7 @@ func (lps *LibraryPanelService) getHandler(c *models.ReqContext) response.Respon
|
||||
func (lps *LibraryPanelService) getAllHandler(c *models.ReqContext) response.Response {
|
||||
libraryPanels, err := lps.getAllLibraryPanels(c, c.QueryInt64("limit"))
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to get library panels", err)
|
||||
return toLibraryPanelError(err, "Failed to get library panels")
|
||||
}
|
||||
|
||||
return response.JSON(200, util.DynMap{"result": libraryPanels})
|
||||
@@ -134,10 +93,7 @@ func (lps *LibraryPanelService) getAllHandler(c *models.ReqContext) response.Res
|
||||
func (lps *LibraryPanelService) getConnectedDashboardsHandler(c *models.ReqContext) response.Response {
|
||||
dashboardIDs, err := lps.getConnectedDashboards(c, c.Params(":uid"))
|
||||
if err != nil {
|
||||
if errors.Is(err, errLibraryPanelNotFound) {
|
||||
return response.Error(404, errLibraryPanelNotFound.Error(), err)
|
||||
}
|
||||
return response.Error(500, "Failed to get connected dashboards", err)
|
||||
return toLibraryPanelError(err, "Failed to get connected dashboards")
|
||||
}
|
||||
|
||||
return response.JSON(200, util.DynMap{"result": dashboardIDs})
|
||||
@@ -147,20 +103,30 @@ func (lps *LibraryPanelService) getConnectedDashboardsHandler(c *models.ReqConte
|
||||
func (lps *LibraryPanelService) patchHandler(c *models.ReqContext, cmd patchLibraryPanelCommand) response.Response {
|
||||
libraryPanel, err := lps.patchLibraryPanel(c, cmd, c.Params(":uid"))
|
||||
if err != nil {
|
||||
if errors.Is(err, errLibraryPanelAlreadyExists) {
|
||||
return response.Error(400, errLibraryPanelAlreadyExists.Error(), err)
|
||||
}
|
||||
if errors.Is(err, errLibraryPanelNotFound) {
|
||||
return response.Error(404, errLibraryPanelNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderNotFound) {
|
||||
return response.Error(404, models.ErrFolderNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderAccessDenied) {
|
||||
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
|
||||
}
|
||||
return response.Error(500, "Failed to update library panel", err)
|
||||
return toLibraryPanelError(err, "Failed to update library panel")
|
||||
}
|
||||
|
||||
return response.JSON(200, util.DynMap{"result": libraryPanel})
|
||||
}
|
||||
|
||||
func toLibraryPanelError(err error, message string) response.Response {
|
||||
if errors.Is(err, errLibraryPanelAlreadyExists) {
|
||||
return response.Error(400, errLibraryPanelAlreadyExists.Error(), err)
|
||||
}
|
||||
if errors.Is(err, errLibraryPanelNotFound) {
|
||||
return response.Error(404, errLibraryPanelNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, errLibraryPanelDashboardNotFound) {
|
||||
return response.Error(404, errLibraryPanelDashboardNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, errLibraryPanelVersionMismatch) {
|
||||
return response.Error(412, errLibraryPanelVersionMismatch.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderNotFound) {
|
||||
return response.Error(404, models.ErrFolderNotFound.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrFolderAccessDenied) {
|
||||
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
|
||||
}
|
||||
return response.Error(500, message, err)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
var (
|
||||
sqlStatmentLibrayPanelDTOWithMeta = `
|
||||
SELECT DISTINCT
|
||||
lp.id, lp.org_id, lp.folder_id, lp.uid, lp.name, lp.model, lp.created, lp.created_by, lp.updated, lp.updated_by
|
||||
lp.id, lp.org_id, lp.folder_id, lp.uid, lp.name, lp.model, lp.created, lp.created_by, lp.updated, lp.updated_by, lp.version
|
||||
, 0 AS can_edit
|
||||
, u1.login AS created_by_name
|
||||
, u1.email AS created_by_email
|
||||
@@ -53,6 +53,7 @@ func (lps *LibraryPanelService) createLibraryPanel(c *models.ReqContext, cmd cre
|
||||
UID: util.GenerateShortUID(),
|
||||
Name: cmd.Name,
|
||||
Model: cmd.Model,
|
||||
Version: 1,
|
||||
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
@@ -85,6 +86,7 @@ func (lps *LibraryPanelService) createLibraryPanel(c *models.ReqContext, cmd cre
|
||||
UID: libraryPanel.UID,
|
||||
Name: libraryPanel.Name,
|
||||
Model: libraryPanel.Model,
|
||||
Version: libraryPanel.Version,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: 0,
|
||||
@@ -340,6 +342,7 @@ func (lps *LibraryPanelService) getLibraryPanel(c *models.ReqContext, uid string
|
||||
UID: libraryPanel.UID,
|
||||
Name: libraryPanel.Name,
|
||||
Model: libraryPanel.Model,
|
||||
Version: libraryPanel.Version,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: libraryPanel.ConnectedDashboards,
|
||||
@@ -395,6 +398,7 @@ func (lps *LibraryPanelService) getAllLibraryPanels(c *models.ReqContext, limit
|
||||
UID: panel.UID,
|
||||
Name: panel.Name,
|
||||
Model: panel.Model,
|
||||
Version: panel.Version,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: panel.ConnectedDashboards,
|
||||
@@ -466,6 +470,7 @@ func (lps *LibraryPanelService) getLibraryPanelsForDashboardID(c *models.ReqCont
|
||||
UID: panel.UID,
|
||||
Name: panel.Name,
|
||||
Model: panel.Model,
|
||||
Version: panel.Version,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: panel.CanEdit,
|
||||
ConnectedDashboards: panel.ConnectedDashboards,
|
||||
@@ -522,6 +527,9 @@ func (lps *LibraryPanelService) patchLibraryPanel(c *models.ReqContext, cmd patc
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if panelInDB.Version != cmd.Version {
|
||||
return errLibraryPanelVersionMismatch
|
||||
}
|
||||
|
||||
var libraryPanel = LibraryPanel{
|
||||
ID: panelInDB.ID,
|
||||
@@ -530,6 +538,7 @@ func (lps *LibraryPanelService) patchLibraryPanel(c *models.ReqContext, cmd patc
|
||||
UID: uid,
|
||||
Name: cmd.Name,
|
||||
Model: cmd.Model,
|
||||
Version: panelInDB.Version + 1,
|
||||
Created: panelInDB.Created,
|
||||
CreatedBy: panelInDB.CreatedBy,
|
||||
Updated: time.Now(),
|
||||
@@ -564,6 +573,7 @@ func (lps *LibraryPanelService) patchLibraryPanel(c *models.ReqContext, cmd patc
|
||||
UID: libraryPanel.UID,
|
||||
Name: libraryPanel.Name,
|
||||
Model: libraryPanel.Model,
|
||||
Version: libraryPanel.Version,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: panelInDB.ConnectedDashboards,
|
||||
|
||||
@@ -102,8 +102,9 @@ func (lps *LibraryPanelService) LoadLibraryPanelsForDashboard(c *models.ReqConte
|
||||
elem.Set("gridPos", panelAsJSON.Get("gridPos").MustMap())
|
||||
elem.Set("id", panelAsJSON.Get("id").MustInt64())
|
||||
elem.Set("libraryPanel", map[string]interface{}{
|
||||
"uid": libraryPanelInDB.UID,
|
||||
"name": libraryPanelInDB.Name,
|
||||
"uid": libraryPanelInDB.UID,
|
||||
"name": libraryPanelInDB.Name,
|
||||
"version": libraryPanelInDB.Version,
|
||||
"meta": map[string]interface{}{
|
||||
"canEdit": libraryPanelInDB.Meta.CanEdit,
|
||||
"connectedDashboards": libraryPanelInDB.Meta.ConnectedDashboards,
|
||||
@@ -246,6 +247,7 @@ func (lps *LibraryPanelService) AddMigration(mg *migrator.Migrator) {
|
||||
{Name: "created_by", Type: migrator.DB_BigInt, Nullable: false},
|
||||
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
|
||||
{Name: "updated_by", Type: migrator.DB_BigInt, Nullable: false},
|
||||
{Name: "version", Type: migrator.DB_BigInt, Nullable: false},
|
||||
},
|
||||
Indices: []*migrator.Index{
|
||||
{Cols: []string{"org_id", "folder_id", "name"}, Type: migrator.UniqueIndex},
|
||||
|
||||
@@ -30,6 +30,7 @@ func TestCreateLibraryPanel(t *testing.T) {
|
||||
"title": "Text - Library Panel",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 1,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: 0,
|
||||
@@ -71,6 +72,7 @@ func TestCreateLibraryPanel(t *testing.T) {
|
||||
"title": "Library Panel Name",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 1,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: 0,
|
||||
|
||||
@@ -49,6 +49,7 @@ func TestGetAllLibraryPanels(t *testing.T) {
|
||||
"title": "Text - Library Panel",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 1,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: 0,
|
||||
@@ -78,6 +79,7 @@ func TestGetAllLibraryPanels(t *testing.T) {
|
||||
"title": "Text - Library Panel2",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 1,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: 0,
|
||||
|
||||
@@ -35,6 +35,7 @@ func TestGetLibraryPanel(t *testing.T) {
|
||||
"title": "Text - Library Panel",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 1,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: 0,
|
||||
|
||||
@@ -37,6 +37,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
"type": "text"
|
||||
}
|
||||
`),
|
||||
Version: 1,
|
||||
}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp = sc.service.patchHandler(sc.reqContext, cmd)
|
||||
@@ -55,6 +56,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
"title": "Panel - New name",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 2,
|
||||
Meta: LibraryPanelDTOMeta{
|
||||
CanEdit: true,
|
||||
ConnectedDashboards: 2,
|
||||
@@ -83,6 +85,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
newFolder := createFolderWithACL(t, "NewFolder", sc.user, []folderACLItem{})
|
||||
cmd := patchLibraryPanelCommand{
|
||||
FolderID: newFolder.Id,
|
||||
Version: 1,
|
||||
}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.patchHandler(sc.reqContext, cmd)
|
||||
@@ -91,6 +94,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
sc.initialResult.Result.FolderID = newFolder.Id
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
@@ -101,6 +105,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
cmd := patchLibraryPanelCommand{
|
||||
FolderID: -1,
|
||||
Name: "New Name",
|
||||
Version: 1,
|
||||
}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.patchHandler(sc.reqContext, cmd)
|
||||
@@ -109,6 +114,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
|
||||
sc.initialResult.Result.Model["title"] = "New Name"
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
@@ -119,6 +125,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
cmd := patchLibraryPanelCommand{
|
||||
FolderID: -1,
|
||||
Model: []byte(`{ "title": "New Model Title", "name": "New Model Name" }`),
|
||||
Version: 1,
|
||||
}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.patchHandler(sc.reqContext, cmd)
|
||||
@@ -129,6 +136,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
}
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
@@ -136,7 +144,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
|
||||
scenarioWithLibraryPanel(t, "When another admin tries to patch a library panel, it should change UpdatedBy successfully and return correct result",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
cmd := patchLibraryPanelCommand{FolderID: -1}
|
||||
cmd := patchLibraryPanelCommand{FolderID: -1, Version: 1}
|
||||
sc.reqContext.UserId = 2
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.patchHandler(sc.reqContext, cmd)
|
||||
@@ -144,6 +152,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
sc.initialResult.Result.Meta.UpdatedBy.ID = int64(2)
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
@@ -155,7 +164,8 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
resp := sc.service.createHandler(sc.reqContext, command)
|
||||
var result = validateAndUnMarshalResponse(t, resp)
|
||||
cmd := patchLibraryPanelCommand{
|
||||
Name: "Text - Library Panel",
|
||||
Name: "Text - Library Panel",
|
||||
Version: 1,
|
||||
}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
|
||||
resp = sc.service.patchHandler(sc.reqContext, cmd)
|
||||
@@ -170,6 +180,7 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
var result = validateAndUnMarshalResponse(t, resp)
|
||||
cmd := patchLibraryPanelCommand{
|
||||
FolderID: 1,
|
||||
Version: 1,
|
||||
}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
|
||||
resp = sc.service.patchHandler(sc.reqContext, cmd)
|
||||
@@ -180,10 +191,24 @@ func TestPatchLibraryPanel(t *testing.T) {
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
cmd := patchLibraryPanelCommand{
|
||||
FolderID: sc.folder.Id,
|
||||
Version: 1,
|
||||
}
|
||||
sc.reqContext.OrgId = 2
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.patchHandler(sc.reqContext, cmd)
|
||||
require.Equal(t, 404, resp.Status())
|
||||
})
|
||||
|
||||
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with an old version number, it should fail",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
cmd := patchLibraryPanelCommand{
|
||||
FolderID: sc.folder.Id,
|
||||
Version: 1,
|
||||
}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.patchHandler(sc.reqContext, cmd)
|
||||
require.Equal(t, 200, resp.Status())
|
||||
resp = sc.service.patchHandler(sc.reqContext, cmd)
|
||||
require.Equal(t, 412, resp.Status())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
|
||||
toFolder := createFolderWithACL(t, "Folder", sc.user, testCase.items)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
cmd := patchLibraryPanelCommand{FolderID: toFolder.Id}
|
||||
cmd := patchLibraryPanelCommand{FolderID: toFolder.Id, Version: 1}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
|
||||
resp = sc.service.patchHandler(sc.reqContext, cmd)
|
||||
require.Equal(t, testCase.status, resp.Status())
|
||||
@@ -100,7 +100,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
|
||||
toFolder := createFolderWithACL(t, "Folder", sc.user, everyonePermissions)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
cmd := patchLibraryPanelCommand{FolderID: toFolder.Id}
|
||||
cmd := patchLibraryPanelCommand{FolderID: toFolder.Id, Version: 1}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
|
||||
resp = sc.service.patchHandler(sc.reqContext, cmd)
|
||||
require.Equal(t, testCase.status, resp.Status())
|
||||
@@ -197,7 +197,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
cmd := patchLibraryPanelCommand{FolderID: 0}
|
||||
cmd := patchLibraryPanelCommand{FolderID: 0, Version: 1}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
|
||||
resp = sc.service.patchHandler(sc.reqContext, cmd)
|
||||
require.Equal(t, testCase.status, resp.Status())
|
||||
@@ -211,7 +211,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
cmd := patchLibraryPanelCommand{FolderID: folder.Id}
|
||||
cmd := patchLibraryPanelCommand{FolderID: folder.Id, Version: 1}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
|
||||
resp = sc.service.patchHandler(sc.reqContext, cmd)
|
||||
require.Equal(t, testCase.status, resp.Status())
|
||||
@@ -307,7 +307,7 @@ func TestLibraryPanelPermissions(t *testing.T) {
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
sc.reqContext.SignedInUser.OrgRole = testCase.role
|
||||
|
||||
cmd := patchLibraryPanelCommand{FolderID: -100}
|
||||
cmd := patchLibraryPanelCommand{FolderID: -100, Version: 1}
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
|
||||
resp = sc.service.patchHandler(sc.reqContext, cmd)
|
||||
require.Equal(t, 404, resp.Status())
|
||||
|
||||
@@ -86,8 +86,9 @@ func TestLoadLibraryPanelsForDashboard(t *testing.T) {
|
||||
},
|
||||
"datasource": "${DS_GDEV-TESTDATA}",
|
||||
"libraryPanel": map[string]interface{}{
|
||||
"uid": sc.initialResult.Result.UID,
|
||||
"name": sc.initialResult.Result.Name,
|
||||
"uid": sc.initialResult.Result.UID,
|
||||
"name": sc.initialResult.Result.Name,
|
||||
"version": sc.initialResult.Result.Version,
|
||||
"meta": map[string]interface{}{
|
||||
"canEdit": false,
|
||||
"connectedDashboards": int64(1),
|
||||
@@ -669,6 +670,7 @@ type libraryPanel struct {
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
Model map[string]interface{} `json:"model"`
|
||||
Version int64 `json:"version"`
|
||||
Meta LibraryPanelDTOMeta `json:"meta"`
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ type LibraryPanel struct {
|
||||
UID string `xorm:"uid"`
|
||||
Name string
|
||||
Model json.RawMessage
|
||||
Version int64
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
@@ -30,6 +31,7 @@ type LibraryPanelWithMeta struct {
|
||||
UID string `xorm:"uid"`
|
||||
Name string
|
||||
Model json.RawMessage
|
||||
Version int64
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
@@ -52,6 +54,7 @@ type LibraryPanelDTO struct {
|
||||
UID string `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
Model json.RawMessage `json:"model"`
|
||||
Version int64 `json:"version"`
|
||||
Meta LibraryPanelDTOMeta `json:"meta"`
|
||||
}
|
||||
|
||||
@@ -98,6 +101,8 @@ var (
|
||||
errLibraryPanelHeaderNameMissing = errors.New("library panel header is missing required property name")
|
||||
// ErrFolderHasConnectedLibraryPanels is an error for when an user deletes a folder that contains connected library panels.
|
||||
ErrFolderHasConnectedLibraryPanels = errors.New("folder contains library panels that are linked to dashboards")
|
||||
// errLibraryPanelVersionMismatch is an error for when a library panel has been changed by someone else.
|
||||
errLibraryPanelVersionMismatch = errors.New("the library panel has been changed by someone else")
|
||||
)
|
||||
|
||||
// Commands
|
||||
@@ -114,4 +119,5 @@ type patchLibraryPanelCommand struct {
|
||||
FolderID int64 `json:"folderId" binding:"Default(-1)"`
|
||||
Name string `json:"name"`
|
||||
Model json.RawMessage `json:"model"`
|
||||
Version int64 `json:"version" binding:"Required"`
|
||||
}
|
||||
|
||||
+2
-3
@@ -5,11 +5,10 @@ import { css } from 'emotion';
|
||||
import { useAsync, useDebounce } from 'react-use';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { usePanelSave } from '../../utils/usePanelSave';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { getLibraryPanelConnectedDashboards, LibraryPanelDTO } from '../../state/api';
|
||||
import { getLibraryPanelConnectedDashboards, PanelModelWithLibraryPanel } from '../../state/api';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel & { libraryPanel: Pick<LibraryPanelDTO, 'uid' | 'name' | 'meta'> };
|
||||
panel: PanelModelWithLibraryPanel;
|
||||
folderId: number;
|
||||
isOpen: boolean;
|
||||
onConfirm: () => void;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
import { PanelModel } from '../../dashboard/state';
|
||||
|
||||
export interface LibraryPanelDTO {
|
||||
id: number;
|
||||
@@ -7,6 +9,7 @@ export interface LibraryPanelDTO {
|
||||
uid: string;
|
||||
name: string;
|
||||
model: any;
|
||||
version: number;
|
||||
meta: LibraryPanelDTOMeta;
|
||||
}
|
||||
|
||||
@@ -25,12 +28,19 @@ export interface LibraryPanelDTOMetaUser {
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface PanelModelWithLibraryPanel extends PanelModel {
|
||||
libraryPanel: Pick<LibraryPanelDTO, 'uid' | 'name' | 'meta' | 'version'>;
|
||||
}
|
||||
|
||||
export async function getLibraryPanels(): Promise<LibraryPanelDTO[]> {
|
||||
const { result } = await getBackendSrv().get(`/api/library-panels`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function addLibraryPanel(panelSaveModel: any, folderId: number): Promise<LibraryPanelDTO> {
|
||||
export async function addLibraryPanel(
|
||||
panelSaveModel: PanelModelWithLibraryPanel,
|
||||
folderId: number
|
||||
): Promise<LibraryPanelDTO> {
|
||||
const { result } = await getBackendSrv().post(`/api/library-panels`, {
|
||||
folderId,
|
||||
name: panelSaveModel.title,
|
||||
@@ -39,11 +49,15 @@ export async function addLibraryPanel(panelSaveModel: any, folderId: number): Pr
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateLibraryPanel(panelSaveModel: any, folderId: number): Promise<LibraryPanelDTO> {
|
||||
export async function updateLibraryPanel(
|
||||
panelSaveModel: PanelModelWithLibraryPanel,
|
||||
folderId: number
|
||||
): Promise<LibraryPanelDTO> {
|
||||
const { result } = await getBackendSrv().patch(`/api/library-panels/${panelSaveModel.libraryPanel.uid}`, {
|
||||
folderId,
|
||||
name: panelSaveModel.title,
|
||||
model: panelSaveModel,
|
||||
version: panelSaveModel.libraryPanel.version,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user