mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Create lib panel and replace it in scene (#88504)
* Create lib panel - also replace it in scene * refactor * refactor * refactor
This commit is contained in:
parent
68f2585712
commit
e86784200a
@ -12,7 +12,7 @@ import {
|
|||||||
behaviors,
|
behaviors,
|
||||||
SceneDataTransformer,
|
SceneDataTransformer,
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { Dashboard, DashboardCursorSync } from '@grafana/schema';
|
import { Dashboard, DashboardCursorSync, LibraryPanel } from '@grafana/schema';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
import { VariablesChanged } from 'app/features/variables/types';
|
import { VariablesChanged } from 'app/features/variables/types';
|
||||||
@ -888,6 +888,76 @@ describe('DashboardScene', () => {
|
|||||||
expect(body.state.children.length).toBe(1);
|
expect(body.state.children.length).toBe(1);
|
||||||
expect(gridItem.state.body).toBeInstanceOf(VizPanel);
|
expect(gridItem.state.body).toBeInstanceOf(VizPanel);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should create a library panel', () => {
|
||||||
|
const gridItem = new DashboardGridItem({
|
||||||
|
key: 'griditem-1',
|
||||||
|
body: new VizPanel({
|
||||||
|
title: 'Panel A',
|
||||||
|
key: 'panel-1',
|
||||||
|
pluginId: 'table',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const scene = buildTestScene({
|
||||||
|
body: new SceneGridLayout({
|
||||||
|
children: [gridItem],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const libPanel = {
|
||||||
|
uid: 'uid',
|
||||||
|
name: 'name',
|
||||||
|
};
|
||||||
|
|
||||||
|
scene.createLibraryPanel(gridItem, libPanel as LibraryPanel);
|
||||||
|
|
||||||
|
const layout = scene.state.body as SceneGridLayout;
|
||||||
|
const newGridItem = layout.state.children[0] as DashboardGridItem;
|
||||||
|
|
||||||
|
expect(layout.state.children.length).toBe(1);
|
||||||
|
expect(newGridItem.state.body).toBeInstanceOf(LibraryVizPanel);
|
||||||
|
expect((newGridItem.state.body as LibraryVizPanel).state.uid).toBe('uid');
|
||||||
|
expect((newGridItem.state.body as LibraryVizPanel).state.name).toBe('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should create a library panel under a row', () => {
|
||||||
|
const gridItem = new DashboardGridItem({
|
||||||
|
key: 'griditem-1',
|
||||||
|
body: new VizPanel({
|
||||||
|
title: 'Panel A',
|
||||||
|
key: 'panel-1',
|
||||||
|
pluginId: 'table',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const scene = buildTestScene({
|
||||||
|
body: new SceneGridLayout({
|
||||||
|
children: [
|
||||||
|
new SceneGridRow({
|
||||||
|
key: 'row-1',
|
||||||
|
children: [gridItem],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const libPanel = {
|
||||||
|
uid: 'uid',
|
||||||
|
name: 'name',
|
||||||
|
};
|
||||||
|
|
||||||
|
scene.createLibraryPanel(gridItem, libPanel as LibraryPanel);
|
||||||
|
|
||||||
|
const layout = scene.state.body as SceneGridLayout;
|
||||||
|
const newGridItem = (layout.state.children[0] as SceneGridRow).state.children[0] as DashboardGridItem;
|
||||||
|
|
||||||
|
expect(layout.state.children.length).toBe(1);
|
||||||
|
expect((layout.state.children[0] as SceneGridRow).state.children.length).toBe(1);
|
||||||
|
expect(newGridItem.state.body).toBeInstanceOf(LibraryVizPanel);
|
||||||
|
expect((newGridItem.state.body as LibraryVizPanel).state.uid).toBe('uid');
|
||||||
|
expect((newGridItem.state.body as LibraryVizPanel).state.name).toBe('name');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
SceneVariableDependencyConfigLike,
|
SceneVariableDependencyConfigLike,
|
||||||
VizPanel,
|
VizPanel,
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { Dashboard, DashboardLink } from '@grafana/schema';
|
import { Dashboard, DashboardLink, LibraryPanel } from '@grafana/schema';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
||||||
import { getNavModel } from 'app/core/selectors/navModel';
|
import { getNavModel } from 'app/core/selectors/navModel';
|
||||||
@ -510,6 +510,53 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public createLibraryPanel(gridItemToReplace: DashboardGridItem, libPanel: LibraryPanel) {
|
||||||
|
const layout = this.state.body;
|
||||||
|
|
||||||
|
if (!(layout instanceof SceneGridLayout)) {
|
||||||
|
throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
|
||||||
|
}
|
||||||
|
|
||||||
|
const panelKey = gridItemToReplace?.state.body.state.key;
|
||||||
|
|
||||||
|
const body = new LibraryVizPanel({
|
||||||
|
title: libPanel.model?.title ?? 'Panel',
|
||||||
|
uid: libPanel.uid,
|
||||||
|
name: libPanel.name,
|
||||||
|
panelKey: panelKey ?? getVizPanelKeyForPanelId(dashboardSceneGraph.getNextPanelId(this)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const newGridItem = gridItemToReplace.clone({ body });
|
||||||
|
|
||||||
|
if (!(newGridItem instanceof DashboardGridItem)) {
|
||||||
|
throw new Error('Could not build library viz panel griditem');
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = gridItemToReplace?.state.key;
|
||||||
|
|
||||||
|
if (gridItemToReplace.parent instanceof SceneGridRow) {
|
||||||
|
const children = gridItemToReplace.parent.state.children.map((rowChild) => {
|
||||||
|
if (rowChild.state.key === key) {
|
||||||
|
return newGridItem;
|
||||||
|
}
|
||||||
|
return rowChild;
|
||||||
|
});
|
||||||
|
|
||||||
|
gridItemToReplace.parent.setState({ children });
|
||||||
|
layout.forceRender();
|
||||||
|
} else {
|
||||||
|
// Find the grid item in the layout and replace it
|
||||||
|
const children = layout.state.children.map((child) => {
|
||||||
|
if (child.state.key === key) {
|
||||||
|
return newGridItem;
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
|
||||||
|
layout.setState({ children });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public duplicatePanel(vizPanel: VizPanel) {
|
public duplicatePanel(vizPanel: VizPanel) {
|
||||||
if (!vizPanel.parent) {
|
if (!vizPanel.parent) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
|
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
|
||||||
|
import { LibraryPanel } from '@grafana/schema/dist/esm/index.gen';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
import { ShareLibraryPanel } from 'app/features/dashboard/components/ShareModal/ShareLibraryPanel';
|
import { ShareLibraryPanel } from 'app/features/dashboard/components/ShareModal/ShareLibraryPanel';
|
||||||
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
|
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
|
||||||
@ -31,11 +32,11 @@ function ShareLibraryPanelTabRenderer({ model }: SceneComponentProps<ShareLibrar
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vizPanel = panelRef.resolve();
|
const parent = panelRef.resolve().parent;
|
||||||
|
|
||||||
if (vizPanel.parent instanceof DashboardGridItem) {
|
if (parent instanceof DashboardGridItem) {
|
||||||
const dashboardScene = dashboardRef.resolve();
|
const dashboardScene = dashboardRef.resolve();
|
||||||
const panelJson = gridItemToPanel(vizPanel.parent);
|
const panelJson = gridItemToPanel(parent);
|
||||||
const panelModel = new PanelModel(panelJson);
|
const panelModel = new PanelModel(panelJson);
|
||||||
|
|
||||||
const dashboardJson = transformSceneToSaveModel(dashboardScene);
|
const dashboardJson = transformSceneToSaveModel(dashboardScene);
|
||||||
@ -49,6 +50,7 @@ function ShareLibraryPanelTabRenderer({ model }: SceneComponentProps<ShareLibrar
|
|||||||
onDismiss={() => {
|
onDismiss={() => {
|
||||||
modalRef?.resolve().onDismiss();
|
modalRef?.resolve().onDismiss();
|
||||||
}}
|
}}
|
||||||
|
onCreateLibraryPanel={(libPanel: LibraryPanel) => dashboardScene.createLibraryPanel(parent, libPanel)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ interface Props extends ShareModalTabProps {
|
|||||||
initialFolderUid?: string;
|
initialFolderUid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShareLibraryPanel = ({ panel, initialFolderUid, onDismiss }: Props) => {
|
export const ShareLibraryPanel = ({ panel, initialFolderUid, onCreateLibraryPanel, onDismiss }: Props) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reportInteraction('grafana_dashboards_library_panel_share_viewed', { shareResource: getTrackingSource(panel) });
|
reportInteraction('grafana_dashboards_library_panel_share_viewed', { shareResource: getTrackingSource(panel) });
|
||||||
}, [panel]);
|
}, [panel]);
|
||||||
@ -25,7 +25,12 @@ export const ShareLibraryPanel = ({ panel, initialFolderUid, onDismiss }: Props)
|
|||||||
<p className="share-modal-info-text">
|
<p className="share-modal-info-text">
|
||||||
<Trans i18nKey="share-modal.library.info">Create library panel.</Trans>
|
<Trans i18nKey="share-modal.library.info">Create library panel.</Trans>
|
||||||
</p>
|
</p>
|
||||||
<AddLibraryPanelContents panel={panel} initialFolderUid={initialFolderUid} onDismiss={onDismiss} />
|
<AddLibraryPanelContents
|
||||||
|
panel={panel}
|
||||||
|
initialFolderUid={initialFolderUid}
|
||||||
|
onCreateLibraryPanel={onCreateLibraryPanel}
|
||||||
|
onDismiss={onDismiss}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { NavModelItem } from '@grafana/data';
|
import { NavModelItem } from '@grafana/data';
|
||||||
|
import { LibraryPanel } from '@grafana/schema/dist/esm/index.gen';
|
||||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||||
|
|
||||||
export interface ShareModalTabProps {
|
export interface ShareModalTabProps {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
panel?: PanelModel;
|
panel?: PanelModel;
|
||||||
onDismiss?(): void;
|
onDismiss?(): void;
|
||||||
|
onCreateLibraryPanel?(libPanel: LibraryPanel): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShareModalTabModel {
|
export interface ShareModalTabModel {
|
||||||
|
@ -2,22 +2,28 @@ import React, { useCallback, useEffect, useState } from 'react';
|
|||||||
import { useAsync, useDebounce } from 'react-use';
|
import { useAsync, useDebounce } from 'react-use';
|
||||||
|
|
||||||
import { FetchError, isFetchError } from '@grafana/runtime';
|
import { FetchError, isFetchError } from '@grafana/runtime';
|
||||||
|
import { LibraryPanel } from '@grafana/schema/dist/esm/index.gen';
|
||||||
import { Button, Field, Input, Modal } from '@grafana/ui';
|
import { Button, Field, Input, Modal } from '@grafana/ui';
|
||||||
import { OldFolderPicker } from 'app/core/components/Select/OldFolderPicker';
|
import { OldFolderPicker } from 'app/core/components/Select/OldFolderPicker';
|
||||||
import { t, Trans } from 'app/core/internationalization';
|
import { t, Trans } from 'app/core/internationalization';
|
||||||
|
|
||||||
import { PanelModel } from '../../../dashboard/state';
|
import { PanelModel } from '../../../dashboard/state';
|
||||||
import { getLibraryPanelByName } from '../../state/api';
|
import { getLibraryPanelByName } from '../../state/api';
|
||||||
import { LibraryElementDTO } from '../../types';
|
|
||||||
import { usePanelSave } from '../../utils/usePanelSave';
|
import { usePanelSave } from '../../utils/usePanelSave';
|
||||||
|
|
||||||
interface AddLibraryPanelContentsProps {
|
interface AddLibraryPanelContentsProps {
|
||||||
onDismiss?: () => void;
|
onDismiss?: () => void;
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
initialFolderUid?: string;
|
initialFolderUid?: string;
|
||||||
|
onCreateLibraryPanel?: (libPanel: LibraryPanel) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddLibraryPanelContents = ({ panel, initialFolderUid, onDismiss }: AddLibraryPanelContentsProps) => {
|
export const AddLibraryPanelContents = ({
|
||||||
|
panel,
|
||||||
|
initialFolderUid,
|
||||||
|
onCreateLibraryPanel,
|
||||||
|
onDismiss,
|
||||||
|
}: AddLibraryPanelContentsProps) => {
|
||||||
const [folderUid, setFolderUid] = useState(initialFolderUid);
|
const [folderUid, setFolderUid] = useState(initialFolderUid);
|
||||||
const [panelName, setPanelName] = useState(panel.title);
|
const [panelName, setPanelName] = useState(panel.title);
|
||||||
const [debouncedPanelName, setDebouncedPanelName] = useState(panel.title);
|
const [debouncedPanelName, setDebouncedPanelName] = useState(panel.title);
|
||||||
@ -30,14 +36,15 @@ export const AddLibraryPanelContents = ({ panel, initialFolderUid, onDismiss }:
|
|||||||
|
|
||||||
const onCreate = useCallback(() => {
|
const onCreate = useCallback(() => {
|
||||||
panel.libraryPanel = { uid: '', name: panelName };
|
panel.libraryPanel = { uid: '', name: panelName };
|
||||||
saveLibraryPanel(panel, folderUid!).then((res: LibraryElementDTO | FetchError) => {
|
saveLibraryPanel(panel, folderUid!).then((res: LibraryPanel | FetchError) => {
|
||||||
if (!isFetchError(res)) {
|
if (!isFetchError(res)) {
|
||||||
onDismiss?.();
|
onDismiss?.();
|
||||||
|
onCreateLibraryPanel?.(res);
|
||||||
} else {
|
} else {
|
||||||
panel.libraryPanel = undefined;
|
panel.libraryPanel = undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [panel, panelName, folderUid, onDismiss, saveLibraryPanel]);
|
}, [panel, panelName, saveLibraryPanel, folderUid, onDismiss, onCreateLibraryPanel]);
|
||||||
|
|
||||||
const isValidName = useAsync(async () => {
|
const isValidName = useAsync(async () => {
|
||||||
try {
|
try {
|
||||||
|
Loading…
Reference in New Issue
Block a user