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
pull/31606/head
Hugo Häggmark 5 years ago committed by GitHub
parent b36314d03f
commit d84cfdbb0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 98
      pkg/services/librarypanels/api.go
  2. 12
      pkg/services/librarypanels/database.go
  3. 6
      pkg/services/librarypanels/librarypanels.go
  4. 2
      pkg/services/librarypanels/librarypanels_create_test.go
  5. 2
      pkg/services/librarypanels/librarypanels_get_all_test.go
  6. 1
      pkg/services/librarypanels/librarypanels_get_test.go
  7. 29
      pkg/services/librarypanels/librarypanels_patch_test.go
  8. 10
      pkg/services/librarypanels/librarypanels_permissions_test.go
  9. 6
      pkg/services/librarypanels/librarypanels_test.go
  10. 6
      pkg/services/librarypanels/models.go
  11. 5
      public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx
  12. 20
      public/app/features/library-panels/state/api.ts

@ -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"`
}

@ -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;
}

Loading…
Cancel
Save