mirror of
https://github.com/grafana/grafana.git
synced 2025-01-10 08:03:58 -06:00
Library Panels: Add name
endpoint & unique name validation to AddLibraryPanelModal (#33987)
This commit is contained in:
parent
d49deebefe
commit
c778d6a4a2
@ -19,6 +19,7 @@ func (l *LibraryElementService) registerAPIEndpoints() {
|
||||
entities.Get("/", middleware.ReqSignedIn, routing.Wrap(l.getAllHandler))
|
||||
entities.Get("/:uid", middleware.ReqSignedIn, routing.Wrap(l.getHandler))
|
||||
entities.Get("/:uid/connections/", middleware.ReqSignedIn, routing.Wrap(l.getConnectionsHandler))
|
||||
entities.Get("/name/:name", middleware.ReqSignedIn, routing.Wrap(l.getByNameHandler))
|
||||
entities.Patch("/:uid", middleware.ReqSignedIn, binding.Bind(patchLibraryElementCommand{}), routing.Wrap(l.patchHandler))
|
||||
})
|
||||
}
|
||||
@ -45,7 +46,7 @@ func (l *LibraryElementService) deleteHandler(c *models.ReqContext) response.Res
|
||||
|
||||
// getHandler handles GET /api/library-elements/:uid.
|
||||
func (l *LibraryElementService) getHandler(c *models.ReqContext) response.Response {
|
||||
element, err := l.getLibraryElement(c, c.Params(":uid"))
|
||||
element, err := l.getLibraryElementByUid(c)
|
||||
if err != nil {
|
||||
return toLibraryElementError(err, "Failed to get library element")
|
||||
}
|
||||
@ -93,6 +94,16 @@ func (l *LibraryElementService) getConnectionsHandler(c *models.ReqContext) resp
|
||||
return response.JSON(200, util.DynMap{"result": connections})
|
||||
}
|
||||
|
||||
// getByNameHandler handles GET /api/library-elements/name/:name/.
|
||||
func (l *LibraryElementService) getByNameHandler(c *models.ReqContext) response.Response {
|
||||
elements, err := l.getLibraryElementsByName(c)
|
||||
if err != nil {
|
||||
return toLibraryElementError(err, "Failed to get library element")
|
||||
}
|
||||
|
||||
return response.JSON(200, util.DynMap{"result": elements})
|
||||
}
|
||||
|
||||
func toLibraryElementError(err error, message string) response.Response {
|
||||
if errors.Is(err, errLibraryElementAlreadyExists) {
|
||||
return response.Error(400, errLibraryElementAlreadyExists.Error(), err)
|
||||
|
@ -187,24 +187,23 @@ func (l *LibraryElementService) deleteLibraryElement(c *models.ReqContext, uid s
|
||||
})
|
||||
}
|
||||
|
||||
// getLibraryElement gets a Library Element.
|
||||
func (l *LibraryElementService) getLibraryElement(c *models.ReqContext, uid string) (LibraryElementDTO, error) {
|
||||
var libraryElement LibraryElementWithMeta
|
||||
// getLibraryElement gets a Library Element where param == value
|
||||
func (l *LibraryElementService) getLibraryElements(c *models.ReqContext, params []Pair) ([]LibraryElementDTO, error) {
|
||||
libraryElements := make([]LibraryElementWithMeta, 0)
|
||||
err := l.SQLStore.WithDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
|
||||
libraryElements := make([]LibraryElementWithMeta, 0)
|
||||
builder := sqlstore.SQLBuilder{}
|
||||
builder.Write(selectLibraryElementDTOWithMeta)
|
||||
builder.Write(", 'General' as folder_name ")
|
||||
builder.Write(", '' as folder_uid ")
|
||||
builder.Write(fromLibraryElementDTOWithMeta)
|
||||
builder.Write(` WHERE le.uid=? AND le.org_id=? AND le.folder_id=0`, uid, c.SignedInUser.OrgId)
|
||||
writeParamSelectorSQL(&builder, append(params, Pair{"folder_id", 0})...)
|
||||
builder.Write(" UNION ")
|
||||
builder.Write(selectLibraryElementDTOWithMeta)
|
||||
builder.Write(", dashboard.title as folder_name ")
|
||||
builder.Write(", dashboard.uid as folder_uid ")
|
||||
builder.Write(fromLibraryElementDTOWithMeta)
|
||||
builder.Write(" INNER JOIN dashboard AS dashboard on le.folder_id = dashboard.id AND le.folder_id <> 0")
|
||||
builder.Write(` WHERE le.uid=? AND le.org_id=?`, uid, c.SignedInUser.OrgId)
|
||||
writeParamSelectorSQL(&builder, params...)
|
||||
if c.SignedInUser.OrgRole != models.ROLE_ADMIN {
|
||||
builder.WriteDashboardPermissionFilter(c.SignedInUser, models.PERMISSION_VIEW)
|
||||
}
|
||||
@ -215,49 +214,65 @@ func (l *LibraryElementService) getLibraryElement(c *models.ReqContext, uid stri
|
||||
if len(libraryElements) == 0 {
|
||||
return errLibraryElementNotFound
|
||||
}
|
||||
if len(libraryElements) > 1 {
|
||||
return fmt.Errorf("found %d elements, while expecting at most one", len(libraryElements))
|
||||
}
|
||||
|
||||
libraryElement = libraryElements[0]
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return []LibraryElementDTO{}, err
|
||||
}
|
||||
|
||||
leDtos := make([]LibraryElementDTO, len(libraryElements))
|
||||
for i, libraryElement := range libraryElements {
|
||||
leDtos[i] = LibraryElementDTO{
|
||||
ID: libraryElement.ID,
|
||||
OrgID: libraryElement.OrgID,
|
||||
FolderID: libraryElement.FolderID,
|
||||
UID: libraryElement.UID,
|
||||
Name: libraryElement.Name,
|
||||
Kind: libraryElement.Kind,
|
||||
Type: libraryElement.Type,
|
||||
Description: libraryElement.Description,
|
||||
Model: libraryElement.Model,
|
||||
Version: libraryElement.Version,
|
||||
Meta: LibraryElementDTOMeta{
|
||||
FolderName: libraryElement.FolderName,
|
||||
FolderUID: libraryElement.FolderUID,
|
||||
ConnectedDashboards: libraryElement.ConnectedDashboards,
|
||||
Created: libraryElement.Created,
|
||||
Updated: libraryElement.Updated,
|
||||
CreatedBy: LibraryElementDTOMetaUser{
|
||||
ID: libraryElement.CreatedBy,
|
||||
Name: libraryElement.CreatedByName,
|
||||
AvatarURL: dtos.GetGravatarUrl(libraryElement.CreatedByEmail),
|
||||
},
|
||||
UpdatedBy: LibraryElementDTOMetaUser{
|
||||
ID: libraryElement.UpdatedBy,
|
||||
Name: libraryElement.UpdatedByName,
|
||||
AvatarURL: dtos.GetGravatarUrl(libraryElement.UpdatedByEmail),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return leDtos, nil
|
||||
}
|
||||
|
||||
// getLibraryElementByUid gets a Library Element by uid.
|
||||
func (l *LibraryElementService) getLibraryElementByUid(c *models.ReqContext) (LibraryElementDTO, error) {
|
||||
libraryElements, err := l.getLibraryElements(c, []Pair{{key: "org_id", value: c.SignedInUser.OrgId}, {key: "uid", value: c.Params(":uid")}})
|
||||
if err != nil {
|
||||
return LibraryElementDTO{}, err
|
||||
}
|
||||
|
||||
dto := LibraryElementDTO{
|
||||
ID: libraryElement.ID,
|
||||
OrgID: libraryElement.OrgID,
|
||||
FolderID: libraryElement.FolderID,
|
||||
UID: libraryElement.UID,
|
||||
Name: libraryElement.Name,
|
||||
Kind: libraryElement.Kind,
|
||||
Type: libraryElement.Type,
|
||||
Description: libraryElement.Description,
|
||||
Model: libraryElement.Model,
|
||||
Version: libraryElement.Version,
|
||||
Meta: LibraryElementDTOMeta{
|
||||
FolderName: libraryElement.FolderName,
|
||||
FolderUID: libraryElement.FolderUID,
|
||||
ConnectedDashboards: libraryElement.ConnectedDashboards,
|
||||
Created: libraryElement.Created,
|
||||
Updated: libraryElement.Updated,
|
||||
CreatedBy: LibraryElementDTOMetaUser{
|
||||
ID: libraryElement.CreatedBy,
|
||||
Name: libraryElement.CreatedByName,
|
||||
AvatarURL: dtos.GetGravatarUrl(libraryElement.CreatedByEmail),
|
||||
},
|
||||
UpdatedBy: LibraryElementDTOMetaUser{
|
||||
ID: libraryElement.UpdatedBy,
|
||||
Name: libraryElement.UpdatedByName,
|
||||
AvatarURL: dtos.GetGravatarUrl(libraryElement.UpdatedByEmail),
|
||||
},
|
||||
},
|
||||
if len(libraryElements) > 1 {
|
||||
return LibraryElementDTO{}, fmt.Errorf("found %d elements, while expecting at most one", len(libraryElements))
|
||||
}
|
||||
|
||||
return dto, nil
|
||||
return libraryElements[0], nil
|
||||
}
|
||||
|
||||
// getLibraryElementByName gets a Library Element by name.
|
||||
func (l *LibraryElementService) getLibraryElementsByName(c *models.ReqContext) ([]LibraryElementDTO, error) {
|
||||
return l.getLibraryElements(c, []Pair{{"org_id", c.SignedInUser.OrgId}, {"name", c.Params(":name")}})
|
||||
}
|
||||
|
||||
// getAllLibraryElements gets all Library Elements.
|
||||
|
@ -14,54 +14,74 @@ import (
|
||||
func TestGetLibraryElement(t *testing.T) {
|
||||
scenarioWithPanel(t, "When an admin tries to get a library panel that does not exist, it should fail",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
// by uid
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown"})
|
||||
resp := sc.service.getHandler(sc.reqContext)
|
||||
require.Equal(t, 404, resp.Status())
|
||||
|
||||
// by name
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":name": "unknown"})
|
||||
resp = sc.service.getByNameHandler(sc.reqContext)
|
||||
require.Equal(t, 404, resp.Status())
|
||||
})
|
||||
|
||||
scenarioWithPanel(t, "When an admin tries to get a library panel that exists, it should succeed and return correct result",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
var expected = func(res libraryElementResult) libraryElementResult {
|
||||
return libraryElementResult{
|
||||
Result: libraryElement{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
FolderID: 1,
|
||||
UID: res.Result.UID,
|
||||
Name: "Text - Library Panel",
|
||||
Kind: int64(Panel),
|
||||
Type: "text",
|
||||
Description: "A description",
|
||||
Model: map[string]interface{}{
|
||||
"datasource": "${DS_GDEV-TESTDATA}",
|
||||
"description": "A description",
|
||||
"id": float64(1),
|
||||
"title": "Text - Library Panel",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 1,
|
||||
Meta: LibraryElementDTOMeta{
|
||||
FolderName: "ScenarioFolder",
|
||||
FolderUID: sc.folder.Uid,
|
||||
ConnectedDashboards: 0,
|
||||
Created: res.Result.Meta.Created,
|
||||
Updated: res.Result.Meta.Updated,
|
||||
CreatedBy: LibraryElementDTOMetaUser{
|
||||
ID: 1,
|
||||
Name: userInDbName,
|
||||
AvatarURL: userInDbAvatar,
|
||||
},
|
||||
UpdatedBy: LibraryElementDTOMetaUser{
|
||||
ID: 1,
|
||||
Name: userInDbName,
|
||||
AvatarURL: userInDbAvatar,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// by uid
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.getHandler(sc.reqContext)
|
||||
var result = validateAndUnMarshalResponse(t, resp)
|
||||
var expected = libraryElementResult{
|
||||
Result: libraryElement{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
FolderID: 1,
|
||||
UID: result.Result.UID,
|
||||
Name: "Text - Library Panel",
|
||||
Kind: int64(Panel),
|
||||
Type: "text",
|
||||
Description: "A description",
|
||||
Model: map[string]interface{}{
|
||||
"datasource": "${DS_GDEV-TESTDATA}",
|
||||
"description": "A description",
|
||||
"id": float64(1),
|
||||
"title": "Text - Library Panel",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 1,
|
||||
Meta: LibraryElementDTOMeta{
|
||||
FolderName: "ScenarioFolder",
|
||||
FolderUID: sc.folder.Uid,
|
||||
ConnectedDashboards: 0,
|
||||
Created: result.Result.Meta.Created,
|
||||
Updated: result.Result.Meta.Updated,
|
||||
CreatedBy: LibraryElementDTOMetaUser{
|
||||
ID: 1,
|
||||
Name: userInDbName,
|
||||
AvatarURL: userInDbAvatar,
|
||||
},
|
||||
UpdatedBy: LibraryElementDTOMetaUser{
|
||||
ID: 1,
|
||||
Name: userInDbName,
|
||||
AvatarURL: userInDbAvatar,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
if diff := cmp.Diff(expected(result), result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
|
||||
|
||||
// by name
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":name": sc.initialResult.Result.Name})
|
||||
resp = sc.service.getByNameHandler(sc.reqContext)
|
||||
arrayResult := validateAndUnMarshalArrayResponse(t, resp)
|
||||
|
||||
if diff := cmp.Diff(libraryElementArrayResult{Result: []libraryElement{expected(result).Result}}, arrayResult, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
@ -102,57 +122,77 @@ func TestGetLibraryElement(t *testing.T) {
|
||||
err := sc.service.ConnectElementsToDashboard(sc.reqContext, []string{sc.initialResult.Result.UID}, dashInDB.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := func(res libraryElementResult) libraryElementResult {
|
||||
return libraryElementResult{
|
||||
Result: libraryElement{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
FolderID: 1,
|
||||
UID: res.Result.UID,
|
||||
Name: "Text - Library Panel",
|
||||
Kind: int64(Panel),
|
||||
Type: "text",
|
||||
Description: "A description",
|
||||
Model: map[string]interface{}{
|
||||
"datasource": "${DS_GDEV-TESTDATA}",
|
||||
"description": "A description",
|
||||
"id": float64(1),
|
||||
"title": "Text - Library Panel",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 1,
|
||||
Meta: LibraryElementDTOMeta{
|
||||
FolderName: "ScenarioFolder",
|
||||
FolderUID: sc.folder.Uid,
|
||||
ConnectedDashboards: 1,
|
||||
Created: res.Result.Meta.Created,
|
||||
Updated: res.Result.Meta.Updated,
|
||||
CreatedBy: LibraryElementDTOMetaUser{
|
||||
ID: 1,
|
||||
Name: userInDbName,
|
||||
AvatarURL: userInDbAvatar,
|
||||
},
|
||||
UpdatedBy: LibraryElementDTOMetaUser{
|
||||
ID: 1,
|
||||
Name: userInDbName,
|
||||
AvatarURL: userInDbAvatar,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// by uid
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.getHandler(sc.reqContext)
|
||||
var result = validateAndUnMarshalResponse(t, resp)
|
||||
var expected = libraryElementResult{
|
||||
Result: libraryElement{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
FolderID: 1,
|
||||
UID: result.Result.UID,
|
||||
Name: "Text - Library Panel",
|
||||
Kind: int64(Panel),
|
||||
Type: "text",
|
||||
Description: "A description",
|
||||
Model: map[string]interface{}{
|
||||
"datasource": "${DS_GDEV-TESTDATA}",
|
||||
"description": "A description",
|
||||
"id": float64(1),
|
||||
"title": "Text - Library Panel",
|
||||
"type": "text",
|
||||
},
|
||||
Version: 1,
|
||||
Meta: LibraryElementDTOMeta{
|
||||
FolderName: "ScenarioFolder",
|
||||
FolderUID: sc.folder.Uid,
|
||||
ConnectedDashboards: 1,
|
||||
Created: result.Result.Meta.Created,
|
||||
Updated: result.Result.Meta.Updated,
|
||||
CreatedBy: LibraryElementDTOMetaUser{
|
||||
ID: 1,
|
||||
Name: userInDbName,
|
||||
AvatarURL: userInDbAvatar,
|
||||
},
|
||||
UpdatedBy: LibraryElementDTOMetaUser{
|
||||
ID: 1,
|
||||
Name: userInDbName,
|
||||
AvatarURL: userInDbAvatar,
|
||||
},
|
||||
},
|
||||
},
|
||||
result := validateAndUnMarshalResponse(t, resp)
|
||||
|
||||
if diff := cmp.Diff(expected(result), result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
|
||||
|
||||
// by name
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":name": sc.initialResult.Result.Name})
|
||||
resp = sc.service.getByNameHandler(sc.reqContext)
|
||||
arrayResult := validateAndUnMarshalArrayResponse(t, resp)
|
||||
if diff := cmp.Diff(libraryElementArrayResult{Result: []libraryElement{expected(result).Result}}, arrayResult, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
scenarioWithPanel(t, "When an admin tries to get a library panel that exists in an other org, it should fail",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
sc.reqContext.SignedInUser.OrgId = 2
|
||||
sc.reqContext.SignedInUser.OrgRole = models.ROLE_ADMIN
|
||||
|
||||
// by uid
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.getHandler(sc.reqContext)
|
||||
require.Equal(t, 404, resp.Status())
|
||||
|
||||
// by name
|
||||
sc.reqContext.ReplaceAllParams(map[string]string{":name": sc.initialResult.Result.Name})
|
||||
resp = sc.service.getByNameHandler(sc.reqContext)
|
||||
require.Equal(t, 404, resp.Status())
|
||||
})
|
||||
}
|
||||
|
@ -110,6 +110,10 @@ type libraryElementResult struct {
|
||||
Result libraryElement `json:"result"`
|
||||
}
|
||||
|
||||
type libraryElementArrayResult struct {
|
||||
Result []libraryElement `json:"result"`
|
||||
}
|
||||
|
||||
type libraryElementsSearch struct {
|
||||
Result libraryElementsSearchResult `json:"result"`
|
||||
}
|
||||
@ -248,6 +252,17 @@ func validateAndUnMarshalResponse(t *testing.T, resp response.Response) libraryE
|
||||
return result
|
||||
}
|
||||
|
||||
func validateAndUnMarshalArrayResponse(t *testing.T, resp response.Response) libraryElementArrayResult {
|
||||
t.Helper()
|
||||
|
||||
require.Equal(t, 200, resp.Status())
|
||||
var result = libraryElementArrayResult{}
|
||||
err := json.Unmarshal(resp.Body(), &result)
|
||||
require.NoError(t, err)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
|
||||
t.Helper()
|
||||
|
||||
|
@ -8,6 +8,28 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
type Pair struct {
|
||||
key string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func selectLibraryElementByParam(params []Pair) (string, []interface{}) {
|
||||
conditions := make([]string, 0, len(params))
|
||||
values := make([]interface{}, 0, len(params))
|
||||
for _, p := range params {
|
||||
conditions = append(conditions, "le."+p.key+"=?")
|
||||
values = append(values, p.value)
|
||||
}
|
||||
return ` WHERE ` + strings.Join(conditions, " AND "), values
|
||||
}
|
||||
|
||||
func writeParamSelectorSQL(builder *sqlstore.SQLBuilder, params ...Pair) {
|
||||
if len(params) > 0 {
|
||||
conditionString, paramValues := selectLibraryElementByParam(params)
|
||||
builder.Write(conditionString, paramValues...)
|
||||
}
|
||||
}
|
||||
|
||||
func writePerPageSQL(query searchLibraryElementsQuery, sqlStore *sqlstore.SQLStore, builder *sqlstore.SQLBuilder) {
|
||||
if query.perPage != 0 {
|
||||
offset := query.perPage * (query.page - 1)
|
||||
|
@ -1,8 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, Field, Input, Modal } from '@grafana/ui';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
import { PanelModel } from '../../../dashboard/state';
|
||||
import { usePanelSave } from '../../utils/usePanelSave';
|
||||
import { useAsync, useDebounce } from 'react-use';
|
||||
import { getLibraryPanelByName } from '../../state/api';
|
||||
|
||||
interface AddLibraryPanelContentsProps {
|
||||
onDismiss: () => void;
|
||||
panel: PanelModel;
|
||||
@ -12,11 +15,42 @@ interface AddLibraryPanelContentsProps {
|
||||
export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: AddLibraryPanelContentsProps) => {
|
||||
const [folderId, setFolderId] = useState(initialFolderId);
|
||||
const [panelTitle, setPanelTitle] = useState(panel.title);
|
||||
const [debouncedPanelTitle, setDebouncedPanelTitle] = useState(panel.title);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
|
||||
useEffect(() => setWaiting(true), [panelTitle]);
|
||||
useDebounce(() => setDebouncedPanelTitle(panelTitle), 350, [panelTitle]);
|
||||
|
||||
const { saveLibraryPanel } = usePanelSave();
|
||||
const onCreate = useCallback(() => {
|
||||
panel.title = panelTitle;
|
||||
saveLibraryPanel(panel, folderId!).then((res) => {
|
||||
if (!(res instanceof Error)) {
|
||||
onDismiss();
|
||||
}
|
||||
});
|
||||
}, [panel, panelTitle, folderId, onDismiss, saveLibraryPanel]);
|
||||
const isValidTitle = useAsync(async () => {
|
||||
try {
|
||||
return !(await getLibraryPanelByName(panelTitle)).some((lp) => lp.folderId === folderId);
|
||||
} catch (err) {
|
||||
err.isHandled = true;
|
||||
return true;
|
||||
} finally {
|
||||
setWaiting(false);
|
||||
}
|
||||
}, [debouncedPanelTitle, folderId]);
|
||||
|
||||
const invalidInput =
|
||||
!isValidTitle?.value && isValidTitle.value !== undefined && panelTitle === debouncedPanelTitle && !waiting;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Field label="Library panel name">
|
||||
<Field
|
||||
label="Library panel name"
|
||||
invalid={invalidInput}
|
||||
error={invalidInput ? 'Library panel with this name already exists' : ''}
|
||||
>
|
||||
<Input name="name" value={panelTitle} onChange={(e) => setPanelTitle(e.currentTarget.value)} />
|
||||
</Field>
|
||||
<Field label="Save in folder" description="Library panel permissions are derived from the folder permissions">
|
||||
@ -27,12 +61,7 @@ export const AddLibraryPanelContents = ({ panel, initialFolderId, onDismiss }: A
|
||||
<Button variant="secondary" onClick={onDismiss} fill="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
panel.title = panelTitle;
|
||||
saveLibraryPanel(panel, folderId!).then(() => onDismiss());
|
||||
}}
|
||||
>
|
||||
<Button onClick={onCreate} disabled={invalidInput}>
|
||||
Create library panel
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
|
@ -48,6 +48,11 @@ export async function getLibraryPanel(uid: string): Promise<LibraryElementDTO> {
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getLibraryPanelByName(name: string): Promise<LibraryElementDTO[]> {
|
||||
const { result } = await getBackendSrv().get<{ result: LibraryElementDTO[] }>(`/api/library-elements/name/${name}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function addLibraryPanel(
|
||||
panelSaveModel: PanelModelWithLibraryPanel,
|
||||
folderId: number
|
||||
|
@ -13,7 +13,12 @@ import { notifyApp } from 'app/core/actions';
|
||||
export const usePanelSave = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [state, saveLibraryPanel] = useAsyncFn(async (panel: PanelModel, folderId: number) => {
|
||||
return await saveAndRefreshLibraryPanel(panel, folderId);
|
||||
try {
|
||||
return await saveAndRefreshLibraryPanel(panel, folderId);
|
||||
} catch (err) {
|
||||
err.isHandled = true;
|
||||
throw new Error(err.data.message);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user