mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
LibraryElements: Adds library elements api and tables (#33741)
* WIP: intial structure * Refactor: adds create library element endpoint * Feature: adds delete library element * wip * Refactor: adds get api * Refactor: adds get all api * Refactor: adds patch api * Refactor: changes to library_element_connection * Refactor: add get connections api * wip: in the middle of refactor * wip * Refactor: consolidating both api:s * Refactor: points front end to library elements api * Tests: Fixes broken test * Fix: fixes delete library elements in folder and adds tests * Refactor: changes order of tabs in manage folder * Refactor: fixes so link does not cover whole card * Update pkg/services/libraryelements/libraryelements.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/libraryelements/libraryelements_permissions_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/libraryelements/database.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Chore: changes after PR comments * Update libraryelements.go * Chore: updates after PR comments Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
co-authored by
Arve Knudsen
parent
9b12e79f3e
commit
f1b2c750e5
@@ -6,7 +6,7 @@ import { StoreState } from '../../types';
|
||||
import { getNavModel } from '../../core/selectors/navModel';
|
||||
import Page from '../../core/components/Page/Page';
|
||||
import { LibraryPanelsSearch } from './components/LibraryPanelsSearch/LibraryPanelsSearch';
|
||||
import { LibraryPanelDTO } from './types';
|
||||
import { LibraryElementDTO } from './types';
|
||||
import { OpenLibraryPanelModal } from './components/OpenLibraryPanelModal/OpenLibraryPanelModal';
|
||||
|
||||
const mapStateToProps = (state: StoreState) => ({
|
||||
@@ -20,7 +20,7 @@ interface OwnProps extends GrafanaRouteComponentProps {}
|
||||
type Props = OwnProps & ConnectedProps<typeof connector>;
|
||||
|
||||
export const LibraryPanelsPage: FC<Props> = ({ navModel }) => {
|
||||
const [selected, setSelected] = useState<LibraryPanelDTO | undefined>(undefined);
|
||||
const [selected, setSelected] = useState<LibraryElementDTO | undefined>(undefined);
|
||||
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
|
||||
+2
-2
@@ -2,14 +2,14 @@ import React, { FC, useEffect, useMemo, useReducer } from 'react';
|
||||
import { Button, Modal, useStyles } from '@grafana/ui';
|
||||
import { LoadingState } from '@grafana/data';
|
||||
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { LibraryElementDTO } from '../../types';
|
||||
import { asyncDispatcher } from '../LibraryPanelsView/actions';
|
||||
import { deleteLibraryPanelModalReducer, initialDeleteLibraryPanelModalState } from './reducer';
|
||||
import { getConnectedDashboards } from './actions';
|
||||
import { getModalStyles } from '../../styles';
|
||||
|
||||
interface Props {
|
||||
libraryPanel: LibraryPanelDTO;
|
||||
libraryPanel: LibraryElementDTO;
|
||||
onConfirm: () => void;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DispatchResult, LibraryPanelDTO } from '../../types';
|
||||
import { DispatchResult, LibraryElementDTO } from '../../types';
|
||||
import { getConnectedDashboards as apiGetConnectedDashboards } from '../../state/api';
|
||||
import { searchCompleted } from './reducer';
|
||||
|
||||
export function getConnectedDashboards(libraryPanel: LibraryPanelDTO): DispatchResult {
|
||||
export function getConnectedDashboards(libraryPanel: LibraryElementDTO): DispatchResult {
|
||||
return async function (dispatch) {
|
||||
const dashboards = await apiGetConnectedDashboards(libraryPanel.uid);
|
||||
dispatch(searchCompleted({ dashboards }));
|
||||
|
||||
+11
-11
@@ -4,14 +4,14 @@ import { GrafanaTheme2, PanelPluginMeta } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Icon, Link, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { LibraryElementDTO } from '../../types';
|
||||
import { PanelTypeCard } from 'app/features/dashboard/components/VizTypePicker/PanelTypeCard';
|
||||
import { DeleteLibraryPanelModal } from '../DeleteLibraryPanelModal/DeleteLibraryPanelModal';
|
||||
|
||||
export interface LibraryPanelCardProps {
|
||||
libraryPanel: LibraryPanelDTO;
|
||||
onClick: (panel: LibraryPanelDTO) => void;
|
||||
onDelete?: (panel: LibraryPanelDTO) => void;
|
||||
libraryPanel: LibraryElementDTO;
|
||||
onClick: (panel: LibraryElementDTO) => void;
|
||||
onDelete?: (panel: LibraryElementDTO) => void;
|
||||
showSecondaryActions?: boolean;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export const LibraryPanelCard: React.FC<LibraryPanelCardProps & { children?: JSX
|
||||
};
|
||||
|
||||
interface FolderLinkProps {
|
||||
libraryPanel: LibraryPanelDTO;
|
||||
libraryPanel: LibraryElementDTO;
|
||||
}
|
||||
|
||||
function FolderLink({ libraryPanel }: FolderLinkProps): JSX.Element {
|
||||
@@ -64,18 +64,18 @@ function FolderLink({ libraryPanel }: FolderLinkProps): JSX.Element {
|
||||
return (
|
||||
<span className={styles.metaContainer}>
|
||||
<Icon name={'folder'} size="sm" />
|
||||
{libraryPanel.meta.folderName}
|
||||
<span>{libraryPanel.meta.folderName}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={`/dashboards/f/${libraryPanel.meta.folderUid}`}>
|
||||
<span className={styles.metaContainer}>
|
||||
<span className={styles.metaContainer}>
|
||||
<Link href={`/dashboards/f/${libraryPanel.meta.folderUid}`}>
|
||||
<Icon name={'folder-upload'} size="sm" />
|
||||
{libraryPanel.meta.folderName}
|
||||
</span>
|
||||
</Link>
|
||||
<span>{libraryPanel.meta.folderName}</span>
|
||||
</Link>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -6,7 +6,7 @@ import { PanelPluginMeta, PluginType } from '@grafana/data';
|
||||
|
||||
import { LibraryPanelsSearch, LibraryPanelsSearchProps } from './LibraryPanelsSearch';
|
||||
import * as api from '../../state/api';
|
||||
import { LibraryPanelSearchResult } from '../../types';
|
||||
import { LibraryElementKind, LibraryElementsSearchResult } from '../../types';
|
||||
import { backendSrv } from '../../../../core/services/backend_srv';
|
||||
import * as viztypepicker from '../../../dashboard/components/VizTypePicker/VizTypePicker';
|
||||
|
||||
@@ -38,7 +38,7 @@ jest.mock('debounce-promise', () => {
|
||||
|
||||
async function getTestContext(
|
||||
propOverrides: Partial<LibraryPanelsSearchProps> = {},
|
||||
searchResult: LibraryPanelSearchResult = { libraryPanels: [], perPage: 40, page: 1, totalCount: 0 }
|
||||
searchResult: LibraryElementsSearchResult = { elements: [], perPage: 40, page: 1, totalCount: 0 }
|
||||
) {
|
||||
jest.clearAllMocks();
|
||||
const pluginInfo: any = { logos: { small: '', large: '' } };
|
||||
@@ -102,7 +102,7 @@ describe('LibraryPanelsSearch', () => {
|
||||
searchString: 'a',
|
||||
folderFilter: [],
|
||||
page: 0,
|
||||
panelFilter: [],
|
||||
typeFilter: [],
|
||||
perPage: 40,
|
||||
});
|
||||
});
|
||||
@@ -130,7 +130,7 @@ describe('LibraryPanelsSearch', () => {
|
||||
sortDirection: 'alpha-desc',
|
||||
folderFilter: [],
|
||||
page: 0,
|
||||
panelFilter: [],
|
||||
typeFilter: [],
|
||||
perPage: 40,
|
||||
});
|
||||
});
|
||||
@@ -158,7 +158,7 @@ describe('LibraryPanelsSearch', () => {
|
||||
searchString: '',
|
||||
folderFilter: [],
|
||||
page: 0,
|
||||
panelFilter: ['graph', 'timeseries'],
|
||||
typeFilter: ['graph', 'timeseries'],
|
||||
perPage: 40,
|
||||
});
|
||||
});
|
||||
@@ -188,7 +188,7 @@ describe('LibraryPanelsSearch', () => {
|
||||
searchString: '',
|
||||
folderFilter: ['0'],
|
||||
page: 0,
|
||||
panelFilter: [],
|
||||
typeFilter: [],
|
||||
perPage: 40,
|
||||
});
|
||||
});
|
||||
@@ -203,10 +203,11 @@ describe('LibraryPanelsSearch', () => {
|
||||
page: 1,
|
||||
totalCount: 1,
|
||||
perPage: 40,
|
||||
libraryPanels: [
|
||||
elements: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Library Panel Name',
|
||||
kind: LibraryElementKind.Panel,
|
||||
uid: 'uid',
|
||||
description: 'Library Panel Description',
|
||||
folderId: 0,
|
||||
@@ -215,7 +216,6 @@ describe('LibraryPanelsSearch', () => {
|
||||
orgId: 1,
|
||||
version: 1,
|
||||
meta: {
|
||||
canEdit: true,
|
||||
folderName: 'General',
|
||||
folderUid: '',
|
||||
connectedDashboards: 0,
|
||||
@@ -247,10 +247,11 @@ describe('LibraryPanelsSearch', () => {
|
||||
page: 1,
|
||||
totalCount: 1,
|
||||
perPage: 40,
|
||||
libraryPanels: [
|
||||
elements: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Library Panel Name',
|
||||
kind: LibraryElementKind.Panel,
|
||||
uid: 'uid',
|
||||
description: 'Library Panel Description',
|
||||
folderId: 0,
|
||||
@@ -259,7 +260,6 @@ describe('LibraryPanelsSearch', () => {
|
||||
orgId: 1,
|
||||
version: 1,
|
||||
meta: {
|
||||
canEdit: true,
|
||||
folderName: 'General',
|
||||
folderUid: '',
|
||||
connectedDashboards: 0,
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import { SortPicker } from '../../../../core/components/Select/SortPicker';
|
||||
import { PanelTypeFilter } from '../../../../core/components/PanelTypeFilter/PanelTypeFilter';
|
||||
import { LibraryPanelsView } from '../LibraryPanelsView/LibraryPanelsView';
|
||||
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../core/constants';
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { LibraryElementDTO } from '../../types';
|
||||
import { FolderFilter } from '../../../../core/components/FolderFilter/FolderFilter';
|
||||
import { FolderInfo } from '../../../../types';
|
||||
import {
|
||||
@@ -25,7 +25,7 @@ export enum LibraryPanelsSearchVariant {
|
||||
}
|
||||
|
||||
export interface LibraryPanelsSearchProps {
|
||||
onClick: (panel: LibraryPanelDTO) => void;
|
||||
onClick: (panel: LibraryElementDTO) => void;
|
||||
variant?: LibraryPanelsSearchVariant;
|
||||
showSort?: boolean;
|
||||
showPanelFilter?: boolean;
|
||||
|
||||
+3
-3
@@ -5,13 +5,13 @@ import { Pagination, useStyles } from '@grafana/ui';
|
||||
import { GrafanaTheme, LoadingState } from '@grafana/data';
|
||||
|
||||
import { LibraryPanelCard } from '../LibraryPanelCard/LibraryPanelCard';
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { LibraryElementDTO } from '../../types';
|
||||
import { changePage, initialLibraryPanelsViewState, libraryPanelsViewReducer } from './reducer';
|
||||
import { asyncDispatcher, deleteLibraryPanel, searchForLibraryPanels } from './actions';
|
||||
|
||||
interface LibraryPanelViewProps {
|
||||
className?: string;
|
||||
onClickCard: (panel: LibraryPanelDTO) => void;
|
||||
onClickCard: (panel: LibraryElementDTO) => void;
|
||||
showSecondaryActions?: boolean;
|
||||
currentPanelId?: string;
|
||||
searchString: string;
|
||||
@@ -58,7 +58,7 @@ export const LibraryPanelsView: React.FC<LibraryPanelViewProps> = ({
|
||||
300,
|
||||
[searchString, sortDirection, panelFilter, folderFilter, page, asyncDispatch]
|
||||
);
|
||||
const onDelete = ({ uid }: LibraryPanelDTO) =>
|
||||
const onDelete = ({ uid }: LibraryElementDTO) =>
|
||||
asyncDispatch(deleteLibraryPanel(uid, { searchString, page, perPage }));
|
||||
const onPageChange = (page: number) => asyncDispatch(changePage({ page }));
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@ export function searchForLibraryPanels(args: SearchArgs): DispatchResult {
|
||||
page: args.page,
|
||||
excludeUid: args.currentPanelId,
|
||||
sortDirection: args.sortDirection,
|
||||
panelFilter: args.panelFilter,
|
||||
typeFilter: args.panelFilter,
|
||||
folderFilter: args.folderFilter,
|
||||
})
|
||||
).pipe(
|
||||
mergeMap(({ perPage, libraryPanels, page, totalCount }) =>
|
||||
mergeMap(({ perPage, elements: libraryPanels, page, totalCount }) =>
|
||||
of(searchCompleted({ libraryPanels, page, perPage, totalCount }))
|
||||
),
|
||||
catchError((err) => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
LibraryPanelsViewState,
|
||||
searchCompleted,
|
||||
} from './reducer';
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { LibraryElementDTO, LibraryElementKind } from '../../types';
|
||||
|
||||
describe('libraryPanelsViewReducer', () => {
|
||||
describe('when initSearch is dispatched', () => {
|
||||
@@ -85,8 +85,8 @@ describe('libraryPanelsViewReducer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function getLibraryPanelMocks(count: number): LibraryPanelDTO[] {
|
||||
const mocks: LibraryPanelDTO[] = [];
|
||||
function getLibraryPanelMocks(count: number): LibraryElementDTO[] {
|
||||
const mocks: LibraryElementDTO[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
mocks.push(
|
||||
@@ -109,7 +109,6 @@ function mockLibraryPanel({
|
||||
name = 'Test Panel',
|
||||
model = { type: 'text', title: 'Test Panel' },
|
||||
meta = {
|
||||
canEdit: true,
|
||||
folderName: 'General',
|
||||
folderUid: '',
|
||||
connectedDashboards: 0,
|
||||
@@ -121,13 +120,14 @@ function mockLibraryPanel({
|
||||
version = 1,
|
||||
description = 'a description',
|
||||
type = 'text',
|
||||
}: Partial<LibraryPanelDTO> = {}): LibraryPanelDTO {
|
||||
}: Partial<LibraryElementDTO> = {}): LibraryElementDTO {
|
||||
return {
|
||||
uid,
|
||||
id,
|
||||
orgId,
|
||||
folderId,
|
||||
name,
|
||||
kind: LibraryElementKind.Panel,
|
||||
model,
|
||||
version,
|
||||
meta,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { LoadingState } from '@grafana/data';
|
||||
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { LibraryElementDTO } from '../../types';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
export interface LibraryPanelsViewState {
|
||||
loadingState: LoadingState;
|
||||
libraryPanels: LibraryPanelDTO[];
|
||||
libraryPanels: LibraryElementDTO[];
|
||||
totalCount: number;
|
||||
perPage: number;
|
||||
page: number;
|
||||
|
||||
+7
-4
@@ -4,14 +4,14 @@ import { AsyncSelect, Button, Modal, useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2, SelectableValue, urlUtil } from '@grafana/data';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { LibraryElementDTO } from '../../types';
|
||||
import { DashboardSearchHit } from '../../../search/types';
|
||||
import { getConnectedDashboards, getLibraryPanelConnectedDashboards } from '../../state/api';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export interface OpenLibraryPanelModalProps {
|
||||
onDismiss: () => void;
|
||||
libraryPanel: LibraryPanelDTO;
|
||||
libraryPanel: LibraryElementDTO;
|
||||
}
|
||||
|
||||
export function OpenLibraryPanelModal({ libraryPanel, onDismiss }: OpenLibraryPanelModalProps): JSX.Element {
|
||||
@@ -47,8 +47,11 @@ export function OpenLibraryPanelModal({ libraryPanel, onDismiss }: OpenLibraryPa
|
||||
{connected > 0 ? (
|
||||
<>
|
||||
<p>
|
||||
This panel is being used in <strong>{connected} dashboards</strong>.Please choose which dashboard to view
|
||||
the panel in:
|
||||
This panel is being used in{' '}
|
||||
<strong>
|
||||
{connected} {connected > 1 ? 'dashboards' : 'dashboard'}
|
||||
</strong>
|
||||
.Please choose which dashboard to view the panel in:
|
||||
</p>
|
||||
<AsyncSelect
|
||||
isClearable
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { AddLibraryPanelModal } from '../AddLibraryPanelModal/AddLibraryPanelModal';
|
||||
import { LibraryPanelsView } from '../LibraryPanelsView/LibraryPanelsView';
|
||||
import { PanelOptionsChangedEvent, PanelQueriesChangedEvent } from 'app/types/events';
|
||||
import { LibraryPanelDTO } from '../../types';
|
||||
import { LibraryElementDTO } from '../../types';
|
||||
import { toPanelModelLibraryPanel } from '../../utils';
|
||||
import { changePanelPlugin } from 'app/features/dashboard/state/actions';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
@@ -23,7 +23,7 @@ interface Props {
|
||||
export const PanelLibraryOptionsGroup: FC<Props> = ({ panel, searchQuery }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const [showingAddPanelModal, setShowingAddPanelModal] = useState(false);
|
||||
const [changeToPanel, setChangeToPanel] = useState<LibraryPanelDTO | undefined>(undefined);
|
||||
const [changeToPanel, setChangeToPanel] = useState<LibraryElementDTO | undefined>(undefined);
|
||||
const [panelFilter, setPanelFilter] = useState<string[]>([]);
|
||||
const onPanelFilterChange = useCallback(
|
||||
(plugins: PanelPluginMeta[]) => {
|
||||
@@ -63,7 +63,7 @@ export const PanelLibraryOptionsGroup: FC<Props> = ({ panel, searchQuery }) => {
|
||||
setShowingAddPanelModal(true);
|
||||
};
|
||||
|
||||
const onChangeLibraryPanel = (panel: LibraryPanelDTO) => {
|
||||
const onChangeLibraryPanel = (panel: LibraryElementDTO) => {
|
||||
setChangeToPanel(panel);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { LibraryPanelDTO, LibraryPanelSearchResult, PanelModelWithLibraryPanel } from '../types';
|
||||
import {
|
||||
LibraryElementConnectionDTO,
|
||||
LibraryElementDTO,
|
||||
LibraryElementKind,
|
||||
LibraryElementsSearchResult,
|
||||
PanelModelWithLibraryPanel,
|
||||
} from '../types';
|
||||
import { DashboardSearchHit } from '../../search/types';
|
||||
import { getBackendSrv } from '../../../core/services/backend_srv';
|
||||
|
||||
@@ -8,7 +14,7 @@ export interface GetLibraryPanelsOptions {
|
||||
page?: number;
|
||||
excludeUid?: string;
|
||||
sortDirection?: string;
|
||||
panelFilter?: string[];
|
||||
typeFilter?: string[];
|
||||
folderFilter?: string[];
|
||||
}
|
||||
|
||||
@@ -18,35 +24,39 @@ export async function getLibraryPanels({
|
||||
page = 1,
|
||||
excludeUid = '',
|
||||
sortDirection = '',
|
||||
panelFilter = [],
|
||||
typeFilter = [],
|
||||
folderFilter = [],
|
||||
}: GetLibraryPanelsOptions = {}): Promise<LibraryPanelSearchResult> {
|
||||
}: GetLibraryPanelsOptions = {}): Promise<LibraryElementsSearchResult> {
|
||||
const params = new URLSearchParams();
|
||||
params.append('searchString', searchString);
|
||||
params.append('sortDirection', sortDirection);
|
||||
params.append('panelFilter', panelFilter.join(','));
|
||||
params.append('typeFilter', typeFilter.join(','));
|
||||
params.append('folderFilter', folderFilter.join(','));
|
||||
params.append('excludeUid', excludeUid);
|
||||
params.append('perPage', perPage.toString(10));
|
||||
params.append('page', page.toString(10));
|
||||
params.append('kind', LibraryElementKind.Panel.toString(10));
|
||||
|
||||
const { result } = await getBackendSrv().get(`/api/library-panels?${params.toString()}`);
|
||||
const { result } = await getBackendSrv().get<{ result: LibraryElementsSearchResult }>(
|
||||
`/api/library-elements?${params.toString()}`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getLibraryPanel(uid: string): Promise<LibraryPanelDTO> {
|
||||
const { result } = await getBackendSrv().get(`/api/library-panels/${uid}`);
|
||||
export async function getLibraryPanel(uid: string): Promise<LibraryElementDTO> {
|
||||
const { result } = await getBackendSrv().get(`/api/library-elements/${uid}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function addLibraryPanel(
|
||||
panelSaveModel: PanelModelWithLibraryPanel,
|
||||
folderId: number
|
||||
): Promise<LibraryPanelDTO> {
|
||||
const { result } = await getBackendSrv().post(`/api/library-panels`, {
|
||||
): Promise<LibraryElementDTO> {
|
||||
const { result } = await getBackendSrv().post(`/api/library-elements`, {
|
||||
folderId,
|
||||
name: panelSaveModel.title,
|
||||
model: panelSaveModel,
|
||||
kind: LibraryElementKind.Panel,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -54,31 +64,36 @@ export async function addLibraryPanel(
|
||||
export async function updateLibraryPanel(
|
||||
panelSaveModel: PanelModelWithLibraryPanel,
|
||||
folderId: number
|
||||
): Promise<LibraryPanelDTO> {
|
||||
const { result } = await getBackendSrv().patch(`/api/library-panels/${panelSaveModel.libraryPanel.uid}`, {
|
||||
): Promise<LibraryElementDTO> {
|
||||
const { result } = await getBackendSrv().patch(`/api/library-elements/${panelSaveModel.libraryPanel.uid}`, {
|
||||
folderId,
|
||||
name: panelSaveModel.title,
|
||||
model: panelSaveModel,
|
||||
version: panelSaveModel.libraryPanel.version,
|
||||
kind: LibraryElementKind.Panel,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function deleteLibraryPanel(uid: string): Promise<{ message: string }> {
|
||||
return getBackendSrv().delete(`/api/library-panels/${uid}`);
|
||||
return getBackendSrv().delete(`/api/library-elements/${uid}`);
|
||||
}
|
||||
|
||||
export async function getLibraryPanelConnectedDashboards(libraryPanelUid: string): Promise<number[]> {
|
||||
const { result } = await getBackendSrv().get(`/api/library-panels/${libraryPanelUid}/dashboards`);
|
||||
export async function getLibraryPanelConnectedDashboards(
|
||||
libraryPanelUid: string
|
||||
): Promise<LibraryElementConnectionDTO[]> {
|
||||
const { result } = await getBackendSrv().get<{ result: LibraryElementConnectionDTO[] }>(
|
||||
`/api/library-elements/${libraryPanelUid}/connections`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getConnectedDashboards(uid: string): Promise<DashboardSearchHit[]> {
|
||||
const dashboardIds = await getLibraryPanelConnectedDashboards(uid);
|
||||
if (dashboardIds.length === 0) {
|
||||
const connections = await getLibraryPanelConnectedDashboards(uid);
|
||||
if (connections.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const searchHits = await getBackendSrv().search({ dashboardIds });
|
||||
const searchHits = await getBackendSrv().search({ dashboardIds: connections.map((c) => c.connectionId) });
|
||||
return searchHits;
|
||||
}
|
||||
|
||||
@@ -2,44 +2,62 @@ import { PanelModel } from '../dashboard/state';
|
||||
import { Dispatch } from 'react';
|
||||
import { AnyAction } from '@reduxjs/toolkit';
|
||||
|
||||
export interface LibraryPanelSearchResult {
|
||||
export enum LibraryElementKind {
|
||||
Panel = 1,
|
||||
Variable,
|
||||
}
|
||||
|
||||
export enum LibraryElementConnectionKind {
|
||||
Dashboard = 1,
|
||||
}
|
||||
|
||||
export interface LibraryElementConnectionDTO {
|
||||
id: number;
|
||||
kind: LibraryElementConnectionKind;
|
||||
elementId: number;
|
||||
connectionId: number;
|
||||
created: string;
|
||||
createdBy: LibraryElementDTOMetaUser;
|
||||
}
|
||||
|
||||
export interface LibraryElementsSearchResult {
|
||||
totalCount: number;
|
||||
libraryPanels: LibraryPanelDTO[];
|
||||
elements: LibraryElementDTO[];
|
||||
perPage: number;
|
||||
page: number;
|
||||
}
|
||||
|
||||
export interface LibraryPanelDTO {
|
||||
export interface LibraryElementDTO {
|
||||
id: number;
|
||||
orgId: number;
|
||||
folderId: number;
|
||||
uid: string;
|
||||
name: string;
|
||||
kind: LibraryElementKind;
|
||||
type: string;
|
||||
description: string;
|
||||
model: any;
|
||||
version: number;
|
||||
meta: LibraryPanelDTOMeta;
|
||||
meta: LibraryElementDTOMeta;
|
||||
}
|
||||
|
||||
export interface LibraryPanelDTOMeta {
|
||||
canEdit: boolean;
|
||||
export interface LibraryElementDTOMeta {
|
||||
folderName: string;
|
||||
folderUid: string;
|
||||
connectedDashboards: number;
|
||||
created: string;
|
||||
updated: string;
|
||||
createdBy: LibraryPanelDTOMetaUser;
|
||||
updatedBy: LibraryPanelDTOMetaUser;
|
||||
createdBy: LibraryElementDTOMetaUser;
|
||||
updatedBy: LibraryElementDTOMetaUser;
|
||||
}
|
||||
|
||||
export interface LibraryPanelDTOMetaUser {
|
||||
export interface LibraryElementDTOMetaUser {
|
||||
id: number;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export type PanelModelLibraryPanel = Pick<LibraryPanelDTO, 'uid' | 'name' | 'meta' | 'version'>;
|
||||
export type PanelModelLibraryPanel = Pick<LibraryElementDTO, 'uid' | 'name' | 'meta' | 'version'>;
|
||||
|
||||
export interface PanelModelWithLibraryPanel extends PanelModel {
|
||||
libraryPanel: PanelModelLibraryPanel;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LibraryPanelDTO, PanelModelLibraryPanel } from './types';
|
||||
import { LibraryElementDTO, PanelModelLibraryPanel } from './types';
|
||||
import { PanelModel } from '../dashboard/state';
|
||||
import { addLibraryPanel, updateLibraryPanel } from './state/api';
|
||||
import { createErrorNotification, createSuccessNotification } from '../../core/copy/appNotification';
|
||||
@@ -12,12 +12,12 @@ export function createPanelLibrarySuccessNotification(message: string): AppNotif
|
||||
return createSuccessNotification(message);
|
||||
}
|
||||
|
||||
export function toPanelModelLibraryPanel(libraryPanelDto: LibraryPanelDTO): PanelModelLibraryPanel {
|
||||
export function toPanelModelLibraryPanel(libraryPanelDto: LibraryElementDTO): PanelModelLibraryPanel {
|
||||
const { uid, name, meta, version } = libraryPanelDto;
|
||||
return { uid, name, meta, version };
|
||||
}
|
||||
|
||||
export async function saveAndRefreshLibraryPanel(panel: PanelModel, folderId: number): Promise<LibraryPanelDTO> {
|
||||
export async function saveAndRefreshLibraryPanel(panel: PanelModel, folderId: number): Promise<LibraryElementDTO> {
|
||||
const panelSaveModel = toPanelSaveModel(panel);
|
||||
const savedPanel = await saveOrUpdateLibraryPanel(panelSaveModel, folderId);
|
||||
updatePanelModelWithUpdate(panel, savedPanel);
|
||||
@@ -37,7 +37,7 @@ function toPanelSaveModel(panel: PanelModel): any {
|
||||
return panelSaveModel;
|
||||
}
|
||||
|
||||
function updatePanelModelWithUpdate(panel: PanelModel, updated: LibraryPanelDTO): void {
|
||||
function updatePanelModelWithUpdate(panel: PanelModel, updated: LibraryElementDTO): void {
|
||||
panel.restoreModel({
|
||||
...updated.model,
|
||||
configRev: 0, // reset config rev, since changes have been saved
|
||||
@@ -46,7 +46,7 @@ function updatePanelModelWithUpdate(panel: PanelModel, updated: LibraryPanelDTO)
|
||||
panel.refresh();
|
||||
}
|
||||
|
||||
function saveOrUpdateLibraryPanel(panel: any, folderId: number): Promise<LibraryPanelDTO> {
|
||||
function saveOrUpdateLibraryPanel(panel: any, folderId: number): Promise<LibraryElementDTO> {
|
||||
if (!panel.libraryPanel) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user