mirror of
https://github.com/grafana/grafana.git
synced 2024-11-27 19:30:36 -06:00
DashboardScene: Share library panel (#78421)
* DashboardScene: Share library panel * Add menu item to create library panel * Test update
This commit is contained in:
parent
91a5c3803c
commit
58a0ff7459
@ -38,7 +38,7 @@ describe('panelMenuBehavior', () => {
|
||||
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
|
||||
expect(menu.state.items?.length).toBe(5);
|
||||
expect(menu.state.items?.length).toBe(6);
|
||||
// verify view panel url keeps url params and adds viewPanel=<panel-key>
|
||||
expect(menu.state.items?.[0].href).toBe('/scenes/dashboard/dash-1?from=now-5m&to=now&viewPanel=panel-12');
|
||||
// verify edit url keeps url time range
|
||||
|
@ -12,6 +12,7 @@ import { getDashboardUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPan
|
||||
import { getPanelIdForVizPanel } from '../utils/utils';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { LibraryVizPanel } from './LibraryVizPanel';
|
||||
import { VizPanelLinks } from './PanelLinks';
|
||||
|
||||
/**
|
||||
@ -24,6 +25,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
|
||||
const panel = menu.parent as VizPanel;
|
||||
const location = locationService.getLocation();
|
||||
const items: PanelMenuItem[] = [];
|
||||
const moreSubMenu: PanelMenuItem[] = [];
|
||||
const panelId = getPanelIdForVizPanel(panel);
|
||||
const dashboard = panel.getRoot();
|
||||
|
||||
@ -63,6 +65,25 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
|
||||
shortcut: 'p s',
|
||||
});
|
||||
|
||||
if (panel instanceof LibraryVizPanel) {
|
||||
// TODO: Implement unlinking library panel
|
||||
} else {
|
||||
moreSubMenu.push({
|
||||
text: t('panel.header-menu.create-library-panel', `Create library panel`),
|
||||
iconClassName: 'share-alt',
|
||||
onClick: () => {
|
||||
reportInteraction('dashboards_panelheader_menu', { item: 'createLibraryPanel' });
|
||||
dashboard.showModal(
|
||||
new ShareModal({
|
||||
panelRef: panel.getRef(),
|
||||
dashboardRef: dashboard.getRef(),
|
||||
activeTab: 'Library panel',
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (config.featureToggles.datatrails) {
|
||||
addDataTrailPanelAction(dashboard, panel, items);
|
||||
}
|
||||
@ -87,6 +108,18 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
|
||||
href: getInspectUrl(panel),
|
||||
});
|
||||
|
||||
if (moreSubMenu.length) {
|
||||
items.push({
|
||||
type: 'submenu',
|
||||
text: t('panel.header-menu.more', `More...`),
|
||||
iconClassName: 'cube',
|
||||
subMenu: moreSubMenu,
|
||||
onClick: (e) => {
|
||||
e.preventDefault();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
menu.setState({ items });
|
||||
};
|
||||
|
||||
|
@ -25,6 +25,7 @@ interface ShareExportTabState extends SceneShareTabState {
|
||||
}
|
||||
|
||||
export class ShareExportTab extends SceneObjectBase<ShareExportTabState> {
|
||||
public tabId = 'Export';
|
||||
static Component = ShareExportTabRenderer;
|
||||
|
||||
private _exporter = new DashboardExporter();
|
||||
|
@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SceneComponentProps, SceneGridItem, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { ShareLibraryPanel } from 'app/features/dashboard/components/ShareModal/ShareLibraryPanel';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
||||
import { gridItemToPanel, transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
||||
|
||||
import { SceneShareTabState } from './types';
|
||||
|
||||
export interface ShareLibraryPanelTabState extends SceneShareTabState {
|
||||
panelRef?: SceneObjectRef<VizPanel>;
|
||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
||||
}
|
||||
|
||||
export class ShareLibraryPanelTab extends SceneObjectBase<ShareLibraryPanelTabState> {
|
||||
public tabId = 'Library panel';
|
||||
static Component = ShareLibraryPanelTabRenderer;
|
||||
|
||||
public getTabLabel() {
|
||||
return t('share-modal.tab-title.library-panel', 'Library panel');
|
||||
}
|
||||
}
|
||||
|
||||
function ShareLibraryPanelTabRenderer({ model }: SceneComponentProps<ShareLibraryPanelTab>) {
|
||||
const { panelRef, dashboardRef, modalRef } = model.useState();
|
||||
|
||||
if (!panelRef) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const vizPanel = panelRef.resolve();
|
||||
|
||||
if (vizPanel.parent instanceof SceneGridItem || vizPanel.parent instanceof PanelRepeaterGridItem) {
|
||||
const dashboardScene = dashboardRef.resolve();
|
||||
const panelJson = gridItemToPanel(vizPanel.parent);
|
||||
const panelModel = new PanelModel(panelJson);
|
||||
|
||||
const dashboardJson = transformSceneToSaveModel(dashboardScene);
|
||||
const dashboardModel = new DashboardModel(dashboardJson);
|
||||
|
||||
return (
|
||||
<ShareLibraryPanel
|
||||
initialFolderUid={dashboardScene.state.meta.folderUid}
|
||||
dashboard={dashboardModel}
|
||||
panel={panelModel}
|
||||
onDismiss={() => {
|
||||
modalRef?.resolve().onDismiss();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@ -30,6 +30,8 @@ interface ShareOptions {
|
||||
}
|
||||
|
||||
export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
|
||||
public tabId = 'Link';
|
||||
|
||||
static Component = ShareLinkTabRenderer;
|
||||
|
||||
constructor(state: Omit<ShareLinkTabState, keyof ShareOptions>) {
|
||||
|
@ -10,6 +10,7 @@ import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { ShareExportTab } from './ShareExportTab';
|
||||
import { ShareLibraryPanelTab } from './ShareLibraryPanelTab';
|
||||
import { ShareLinkTab } from './ShareLinkTab';
|
||||
import { SharePanelEmbedTab } from './SharePanelEmbedTab';
|
||||
import { ShareSnapshotTab } from './ShareSnapshotTab';
|
||||
@ -51,34 +52,19 @@ export class ShareModal extends SceneObjectBase<ShareModalState> implements Moda
|
||||
tabs.push(new ShareSnapshotTab({ panelRef, dashboardRef, modalRef: this.getRef() }));
|
||||
}
|
||||
|
||||
if (panelRef) {
|
||||
tabs.push(new SharePanelEmbedTab({ panelRef, dashboardRef }));
|
||||
|
||||
if (panelRef.resolve() instanceof VizPanel) {
|
||||
tabs.push(new ShareLibraryPanelTab({ panelRef, dashboardRef, modalRef: this.getRef() }));
|
||||
}
|
||||
}
|
||||
|
||||
if (Boolean(config.featureToggles['publicDashboards'])) {
|
||||
tabs.push(new SharePublicDashboardTab({ dashboardRef, modalRef: this.getRef() }));
|
||||
}
|
||||
|
||||
if (panelRef) {
|
||||
tabs.push(new SharePanelEmbedTab({ panelRef, dashboardRef }));
|
||||
}
|
||||
|
||||
this.setState({ tabs });
|
||||
|
||||
// if (panel) {
|
||||
// const embedLabel = t('share-modal.tab-title.embed', 'Embed');
|
||||
// tabs.push({ label: embedLabel, value: shareDashboardType.embed, component: ShareEmbed });
|
||||
|
||||
// if (!isPanelModelLibraryPanel(panel)) {
|
||||
// const libraryPanelLabel = t('share-modal.tab-title.library-panel', 'Library panel');
|
||||
// tabs.push({ label: libraryPanelLabel, value: shareDashboardType.libraryPanel, component: ShareLibraryPanel });
|
||||
// }
|
||||
// tabs.push(...customPanelTabs);
|
||||
// } else {
|
||||
// const exportLabel = t('share-modal.tab-title.export', 'Export');
|
||||
// tabs.push({
|
||||
// label: exportLabel,
|
||||
// value: shareDashboardType.export,
|
||||
// component: ShareExport,
|
||||
// });
|
||||
// tabs.push(...customDashboardTabs);
|
||||
// }
|
||||
}
|
||||
|
||||
onDismiss = () => {
|
||||
@ -101,7 +87,7 @@ function SharePanelModalRenderer({ model }: SceneComponentProps<ShareModal>) {
|
||||
|
||||
const modalTabs = tabs?.map((tab) => ({
|
||||
label: tab.getTabLabel(),
|
||||
value: tab.getTabLabel(),
|
||||
value: tab.tabId,
|
||||
}));
|
||||
|
||||
const header = (
|
||||
@ -114,7 +100,7 @@ function SharePanelModalRenderer({ model }: SceneComponentProps<ShareModal>) {
|
||||
/>
|
||||
);
|
||||
|
||||
const currentTab = tabs.find((t) => t.getTabLabel() === activeTab);
|
||||
const currentTab = tabs.find((t) => t.tabId === activeTab);
|
||||
|
||||
return (
|
||||
<Modal isOpen={true} title={header} onDismiss={model.onDismiss}>
|
||||
|
@ -18,6 +18,7 @@ export interface SharePanelEmbedTabState extends SceneShareTabState {
|
||||
}
|
||||
|
||||
export class SharePanelEmbedTab extends SceneObjectBase<SharePanelEmbedTabState> {
|
||||
public tabId = 'Embed';
|
||||
static Component = SharePanelEmbedTabRenderer;
|
||||
|
||||
public constructor(state: SharePanelEmbedTabState) {
|
||||
|
@ -50,6 +50,7 @@ export interface ShareSnapshotTabState extends SceneShareTabState {
|
||||
}
|
||||
|
||||
export class ShareSnapshotTab extends SceneObjectBase<ShareSnapshotTabState> {
|
||||
public tabId = 'Snapshot';
|
||||
static Component = ShareSnapshoTabRenderer;
|
||||
|
||||
public constructor(state: ShareSnapshotTabState) {
|
||||
|
@ -17,6 +17,7 @@ export interface SharePublicDashboardTabState extends SceneShareTabState {
|
||||
}
|
||||
|
||||
export class SharePublicDashboardTab extends SceneObjectBase<SharePublicDashboardTabState> {
|
||||
public tabId = 'Public dashboard';
|
||||
static Component = SharePublicDashboardTabRenderer;
|
||||
|
||||
public getTabLabel() {
|
||||
|
@ -10,4 +10,5 @@ export interface SceneShareTabState extends SceneObjectState {
|
||||
|
||||
export interface SceneShareTab<T extends SceneShareTabState = SceneShareTabState> extends SceneObject<T> {
|
||||
getTabLabel(): string;
|
||||
tabId: string;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export const ShareLibraryPanel = ({ panel, initialFolderUid, onDismiss }: Props)
|
||||
<p className="share-modal-info-text">
|
||||
<Trans i18nKey="share-modal.library.info">Create library panel.</Trans>
|
||||
</p>
|
||||
<AddLibraryPanelContents panel={panel} initialFolderUid={initialFolderUid} onDismiss={onDismiss!} />
|
||||
<AddLibraryPanelContents panel={panel} initialFolderUid={initialFolderUid} onDismiss={onDismiss} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ import { LibraryElementDTO } from '../../types';
|
||||
import { usePanelSave } from '../../utils/usePanelSave';
|
||||
|
||||
interface AddLibraryPanelContentsProps {
|
||||
onDismiss: () => void;
|
||||
onDismiss?: () => void;
|
||||
panel: PanelModel;
|
||||
initialFolderUid?: string;
|
||||
}
|
||||
@ -23,20 +23,23 @@ export const AddLibraryPanelContents = ({ panel, initialFolderUid, onDismiss }:
|
||||
const [debouncedPanelName, setDebouncedPanelName] = useState(panel.title);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
|
||||
console.log('folderUid', folderUid);
|
||||
useEffect(() => setWaiting(true), [panelName]);
|
||||
useDebounce(() => setDebouncedPanelName(panelName), 350, [panelName]);
|
||||
|
||||
const { saveLibraryPanel } = usePanelSave();
|
||||
|
||||
const onCreate = useCallback(() => {
|
||||
panel.libraryPanel = { uid: '', name: panelName };
|
||||
saveLibraryPanel(panel, folderUid!).then((res: LibraryElementDTO | FetchError) => {
|
||||
if (!isFetchError(res)) {
|
||||
onDismiss();
|
||||
onDismiss?.();
|
||||
} else {
|
||||
panel.libraryPanel = undefined;
|
||||
}
|
||||
});
|
||||
}, [panel, panelName, folderUid, onDismiss, saveLibraryPanel]);
|
||||
|
||||
const isValidName = useAsync(async () => {
|
||||
try {
|
||||
return !(await getLibraryPanelByName(panelName)).some((lp) => lp.folderUid === folderUid);
|
||||
@ -50,6 +53,7 @@ export const AddLibraryPanelContents = ({ panel, initialFolderUid, onDismiss }:
|
||||
}
|
||||
}, [debouncedPanelName, folderUid]);
|
||||
|
||||
console.log('isValidName:', isValidName);
|
||||
const invalidInput =
|
||||
!isValidName?.value && isValidName.value !== undefined && panelName === debouncedPanelName && !waiting;
|
||||
|
||||
|
@ -3303,7 +3303,7 @@ __metadata:
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes@npm:1.24.1":
|
||||
version: 1.24.1
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/scenes@npm:1.24.1"
|
||||
dependencies:
|
||||
"@grafana/e2e-selectors": "npm:10.0.2"
|
||||
@ -3318,7 +3318,7 @@ __metadata:
|
||||
"@grafana/ui": 10.0.3
|
||||
checksum: 38967dd3977a9b9feb4c295da58bb378d2f9c810c43b34c67c65858782b9593421dfb58d29bc3fa0e86fc63f9af37f7a381ac958ef43bf38d6443a0a7e4de059
|
||||
languageName: node
|
||||
linkType: hard
|
||||
linkType: soft
|
||||
|
||||
"@grafana/schema@npm:10.3.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
|
||||
version: 0.0.0-use.local
|
||||
|
Loading…
Reference in New Issue
Block a user