Dashboard scenes: Remove panel menu options that are dashboard editing activities when not in edit mode. (#84156)

* remove panel menu options that are dasbhoard editing activities when the dashboard is not in edit mode

* remove corresponding keybindings when not in edit mode

* add keyboard shortcuts but inactivate them when not in edit mode

* Add tests; fix tests
This commit is contained in:
Oscar Kilhed 2024-03-11 11:33:33 +01:00 committed by GitHub
parent e8ecbaffc2
commit 87d6bebb9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 103 additions and 56 deletions

View File

@ -70,7 +70,7 @@ describe('panelMenuBehavior', () => {
await new Promise((r) => setTimeout(r, 1)); await new Promise((r) => setTimeout(r, 1));
expect(menu.state.items?.length).toBe(8); expect(menu.state.items?.length).toBe(6);
// verify view panel url keeps url params and adds viewPanel=<panel-key> // verify view panel url keeps url params and adds viewPanel=<panel-key>
expect(menu.state.items?.[0].href).toBe('/d/dash-1?from=now-5m&to=now&viewPanel=panel-12'); expect(menu.state.items?.[0].href).toBe('/d/dash-1?from=now-5m&to=now&viewPanel=panel-12');
// verify edit url keeps url time range // verify edit url keeps url time range
@ -119,7 +119,7 @@ describe('panelMenuBehavior', () => {
await new Promise((r) => setTimeout(r, 1)); await new Promise((r) => setTimeout(r, 1));
expect(menu.state.items?.length).toBe(9); expect(menu.state.items?.length).toBe(7);
const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu; const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu;
@ -158,7 +158,7 @@ describe('panelMenuBehavior', () => {
await new Promise((r) => setTimeout(r, 1)); await new Promise((r) => setTimeout(r, 1));
expect(menu.state.items?.length).toBe(9); expect(menu.state.items?.length).toBe(7);
const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu; const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu;
@ -199,7 +199,7 @@ describe('panelMenuBehavior', () => {
await new Promise((r) => setTimeout(r, 1)); await new Promise((r) => setTimeout(r, 1));
expect(menu.state.items?.length).toBe(9); expect(menu.state.items?.length).toBe(7);
const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu; const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu;
const menuItem = extensionsSubMenu?.find((i) => (i.text = 'Declare incident when...')); const menuItem = extensionsSubMenu?.find((i) => (i.text = 'Declare incident when...'));
@ -347,7 +347,7 @@ describe('panelMenuBehavior', () => {
await new Promise((r) => setTimeout(r, 1)); await new Promise((r) => setTimeout(r, 1));
expect(menu.state.items?.length).toBe(9); expect(menu.state.items?.length).toBe(7);
const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu; const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu;
@ -392,7 +392,7 @@ describe('panelMenuBehavior', () => {
await new Promise((r) => setTimeout(r, 1)); await new Promise((r) => setTimeout(r, 1));
expect(menu.state.items?.length).toBe(9); expect(menu.state.items?.length).toBe(7);
const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu; const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu;
@ -445,7 +445,7 @@ describe('panelMenuBehavior', () => {
await new Promise((r) => setTimeout(r, 1)); await new Promise((r) => setTimeout(r, 1));
expect(menu.state.items?.length).toBe(9); expect(menu.state.items?.length).toBe(7);
const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu; const extensionsSubMenu = menu.state.items?.find((i) => i.text === 'Extensions')?.subMenu;
@ -470,6 +470,43 @@ describe('panelMenuBehavior', () => {
); );
}); });
it('it should not contain remove and duplicate menu items when not in edit mode', async () => {
const { menu, panel } = await buildTestScene({});
panel.getPlugin = () => getPanelPlugin({ skipDataQuery: false });
mocks.contextSrv.hasAccessToExplore.mockReturnValue(true);
mocks.getExploreUrl.mockReturnValue(Promise.resolve('/explore'));
menu.activate();
await new Promise((r) => setTimeout(r, 1));
expect(menu.state.items?.find((i) => i.text === 'Remove')).toBeUndefined();
const moreMenu = menu.state.items?.find((i) => i.text === 'More...')?.subMenu;
expect(moreMenu?.find((i) => i.text === 'Duplicate')).toBeUndefined();
expect(moreMenu?.find((i) => i.text === 'Create library panel')).toBeUndefined();
});
it('it should contain remove and duplicate menu items when in edit mode', async () => {
const { scene, menu, panel } = await buildTestScene({});
scene.setState({ isEditing: true });
panel.getPlugin = () => getPanelPlugin({ skipDataQuery: false });
mocks.contextSrv.hasAccessToExplore.mockReturnValue(true);
mocks.getExploreUrl.mockReturnValue(Promise.resolve('/explore'));
menu.activate();
await new Promise((r) => setTimeout(r, 1));
expect(menu.state.items?.find((i) => i.text === 'Remove')).toBeDefined();
const moreMenu = menu.state.items?.find((i) => i.text === 'More...')?.subMenu;
expect(moreMenu?.find((i) => i.text === 'Duplicate')).toBeDefined();
expect(moreMenu?.find((i) => i.text === 'Create library panel')).toBeDefined();
});
it('should only contain explore when embedded', async () => { it('should only contain explore when embedded', async () => {
const { menu, panel } = await buildTestScene({ isEmbedded: true }); const { menu, panel } = await buildTestScene({ isEmbedded: true });

View File

@ -86,14 +86,16 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
shortcut: 'p s', shortcut: 'p s',
}); });
moreSubMenu.push({ if (dashboard.state.isEditing) {
text: t('panel.header-menu.duplicate', `Duplicate`), moreSubMenu.push({
onClick: () => { text: t('panel.header-menu.duplicate', `Duplicate`),
DashboardInteractions.panelMenuItemClicked('duplicate'); onClick: () => {
dashboard.duplicatePanel(panel); DashboardInteractions.panelMenuItemClicked('duplicate');
}, dashboard.duplicatePanel(panel);
shortcut: 'p d', },
}); shortcut: 'p d',
});
}
moreSubMenu.push({ moreSubMenu.push({
text: t('panel.header-menu.copy', `Copy`), text: t('panel.header-menu.copy', `Copy`),
@ -103,32 +105,34 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
}, },
}); });
if (parent instanceof LibraryVizPanel) { if (dashboard.state.isEditing) {
moreSubMenu.push({ if (parent instanceof LibraryVizPanel) {
text: t('panel.header-menu.unlink-library-panel', `Unlink library panel`), moreSubMenu.push({
onClick: () => { text: t('panel.header-menu.unlink-library-panel', `Unlink library panel`),
DashboardInteractions.panelMenuItemClicked('unlinkLibraryPanel'); onClick: () => {
dashboard.showModal( DashboardInteractions.panelMenuItemClicked('unlinkLibraryPanel');
new UnlinkLibraryPanelModal({ dashboard.showModal(
panelRef: parent.getRef(), new UnlinkLibraryPanelModal({
}) panelRef: parent.getRef(),
); })
}, );
}); },
} else { });
moreSubMenu.push({ } else {
text: t('panel.header-menu.create-library-panel', `Create library panel`), moreSubMenu.push({
onClick: () => { text: t('panel.header-menu.create-library-panel', `Create library panel`),
DashboardInteractions.panelMenuItemClicked('createLibraryPanel'); onClick: () => {
dashboard.showModal( DashboardInteractions.panelMenuItemClicked('createLibraryPanel');
new ShareModal({ dashboard.showModal(
panelRef: panel.getRef(), new ShareModal({
dashboardRef: dashboard.getRef(), panelRef: panel.getRef(),
activeTab: shareDashboardType.libraryPanel, dashboardRef: dashboard.getRef(),
}) activeTab: shareDashboardType.libraryPanel,
); })
}, );
}); },
});
}
} }
moreSubMenu.push({ moreSubMenu.push({
@ -196,20 +200,22 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
}); });
} }
items.push({ if (dashboard.state.isEditing) {
text: '', items.push({
type: 'divider', text: '',
}); type: 'divider',
});
items.push({ items.push({
text: t('panel.header-menu.remove', `Remove`), text: t('panel.header-menu.remove', `Remove`),
iconClassName: 'trash-alt', iconClassName: 'trash-alt',
onClick: () => { onClick: () => {
DashboardInteractions.panelMenuItemClicked('remove'); DashboardInteractions.panelMenuItemClicked('remove');
onRemovePanel(dashboard, panel); onRemovePanel(dashboard, panel);
}, },
shortcut: 'p r', shortcut: 'p r',
}); });
}
menu.setState({ items }); menu.setState({ items });
}; };

View File

@ -120,7 +120,9 @@ export function setupKeyboardShortcuts(scene: DashboardScene) {
keybindings.addBinding({ keybindings.addBinding({
key: 'p r', key: 'p r',
onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => { onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => {
onRemovePanel(scene, vizPanel); if (scene.state.isEditing) {
onRemovePanel(scene, vizPanel);
}
}), }),
}); });
@ -128,7 +130,9 @@ export function setupKeyboardShortcuts(scene: DashboardScene) {
keybindings.addBinding({ keybindings.addBinding({
key: 'p d', key: 'p d',
onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => { onTrigger: withFocusedPanel(scene, (vizPanel: VizPanel) => {
scene.duplicatePanel(vizPanel); if (scene.state.isEditing) {
scene.duplicatePanel(vizPanel);
}
}), }),
}); });