mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardScene: Action toolbar progress (#81664)
* DashboardScene: Action toolbar progress * Add discard confirmation modal * minor fix * Update * tweaked * Updating * Progress * Update * Update * Added some unit tests * fix test * Change name to Exit edit * Tweaks * fix test * Minor margin fix * Move share to left of edit
This commit is contained in:
parent
9b18a4d45e
commit
61c7fcc270
@ -117,7 +117,7 @@ const getStyles = (theme: GrafanaTheme2, overflowButtonOrder: number, alignment:
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: theme.colors.background.primary,
|
backgroundColor: theme.colors.background.primary,
|
||||||
borderRadius: theme.shape.radius.default,
|
borderRadius: theme.shape.radius.default,
|
||||||
boxShadow: theme.shadows.z3,
|
boxShadow: theme.shadows.z2,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
@ -128,7 +128,7 @@ const getStyles = (theme: GrafanaTheme2, overflowButtonOrder: number, alignment:
|
|||||||
right: 0,
|
right: 0,
|
||||||
top: '100%',
|
top: '100%',
|
||||||
width: 'max-content',
|
width: 'max-content',
|
||||||
zIndex: theme.zIndex.sidemenu,
|
zIndex: theme.zIndex.dropdown,
|
||||||
}),
|
}),
|
||||||
container: css({
|
container: css({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -62,7 +62,6 @@ export function NavToolbar({
|
|||||||
<Breadcrumbs breadcrumbs={breadcrumbs} className={styles.breadcrumbsWrapper} />
|
<Breadcrumbs breadcrumbs={breadcrumbs} className={styles.breadcrumbsWrapper} />
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
{actions}
|
{actions}
|
||||||
{actions && <NavToolbarSeparator />}
|
|
||||||
{searchBarHidden && (
|
{searchBarHidden && (
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
onClick={onToggleKioskMode}
|
onClick={onToggleKioskMode}
|
||||||
@ -71,6 +70,7 @@ export function NavToolbar({
|
|||||||
icon="monitor"
|
icon="monitor"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{actions && <NavToolbarSeparator />}
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
onClick={onToggleSearchBar}
|
onClick={onToggleSearchBar}
|
||||||
narrow
|
narrow
|
||||||
|
@ -12,7 +12,7 @@ export function initAlerting() {
|
|||||||
addCustomRightAction({
|
addCustomRightAction({
|
||||||
show: () => config.unifiedAlertingEnabled,
|
show: () => config.unifiedAlertingEnabled,
|
||||||
component: ({ dashboard }) => (
|
component: ({ dashboard }) => (
|
||||||
<React.Suspense fallback={null}>
|
<React.Suspense fallback={null} key="alert-rules-button">
|
||||||
{dashboard && <AlertRulesToolbarButton dashboardUid={dashboard.uid} />}
|
{dashboard && <AlertRulesToolbarButton dashboardUid={dashboard.uid} />}
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
),
|
),
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { SceneObjectState, SceneObject, SceneObjectBase, SceneComponentProps } from '@grafana/scenes';
|
import { SceneObjectState, SceneObject, SceneObjectBase, SceneComponentProps } from '@grafana/scenes';
|
||||||
import { Box, Stack, ToolbarButton } from '@grafana/ui';
|
import { Box, Stack } from '@grafana/ui';
|
||||||
|
|
||||||
import { getDashboardSceneFor } from '../utils/utils';
|
|
||||||
|
|
||||||
import { DashboardLinksControls } from './DashboardLinksControls';
|
import { DashboardLinksControls } from './DashboardLinksControls';
|
||||||
|
|
||||||
@ -18,9 +16,7 @@ export class DashboardControls extends SceneObjectBase<DashboardControlsState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardControls>) {
|
function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardControls>) {
|
||||||
const dashboard = getDashboardSceneFor(model);
|
|
||||||
const { variableControls, linkControls, timeControls, hideTimeControls } = model.useState();
|
const { variableControls, linkControls, timeControls, hideTimeControls } = model.useState();
|
||||||
const { isEditing } = dashboard.useState();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
@ -38,9 +34,6 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
|
|||||||
<linkControls.Component model={linkControls} />
|
<linkControls.Component model={linkControls} />
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack justifyContent={'flex-end'}>
|
<Stack justifyContent={'flex-end'}>
|
||||||
{isEditing && (
|
|
||||||
<ToolbarButton variant="canvas" icon="cog" tooltip="Dashboard settings" onClick={dashboard.onOpenSettings} />
|
|
||||||
)}
|
|
||||||
{!hideTimeControls && timeControls.map((c) => <c.Component model={c} key={c.state.key} />)}
|
{!hideTimeControls && timeControls.map((c) => <c.Component model={c} key={c.state.key} />)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -57,7 +57,7 @@ describe('DashboardScene', () => {
|
|||||||
|
|
||||||
expect(scene.state.isDirty).toBe(true);
|
expect(scene.state.isDirty).toBe(true);
|
||||||
|
|
||||||
scene.onDiscard();
|
scene.exitEditMode({ skipConfirm: true });
|
||||||
const gridItem2 = sceneGraph.findObject(scene, (p) => p.state.key === 'griditem-1') as SceneGridItem;
|
const gridItem2 = sceneGraph.findObject(scene, (p) => p.state.key === 'griditem-1') as SceneGridItem;
|
||||||
expect(gridItem2.state.x).toBe(0);
|
expect(gridItem2.state.x).toBe(0);
|
||||||
});
|
});
|
||||||
@ -77,7 +77,7 @@ describe('DashboardScene', () => {
|
|||||||
|
|
||||||
expect(scene.state.isDirty).toBe(true);
|
expect(scene.state.isDirty).toBe(true);
|
||||||
|
|
||||||
scene.onDiscard();
|
scene.exitEditMode({ skipConfirm: true });
|
||||||
expect(scene.state[prop]).toEqual(prevState);
|
expect(scene.state[prop]).toEqual(prevState);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -89,7 +89,7 @@ describe('DashboardScene', () => {
|
|||||||
|
|
||||||
expect(scene.state.isDirty).toBe(true);
|
expect(scene.state.isDirty).toBe(true);
|
||||||
|
|
||||||
scene.onDiscard();
|
scene.exitEditMode({ skipConfirm: true });
|
||||||
expect(dashboardSceneGraph.getRefreshPicker(scene)!.state.intervals).toEqual(prevState);
|
expect(dashboardSceneGraph.getRefreshPicker(scene)!.state.intervals).toEqual(prevState);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ describe('DashboardScene', () => {
|
|||||||
|
|
||||||
expect(scene.state.isDirty).toBe(true);
|
expect(scene.state.isDirty).toBe(true);
|
||||||
|
|
||||||
scene.onDiscard();
|
scene.exitEditMode({ skipConfirm: true });
|
||||||
expect(dashboardSceneGraph.getDashboardControls(scene)!.state.hideTimeControls).toEqual(prevState);
|
expect(dashboardSceneGraph.getDashboardControls(scene)!.state.hideTimeControls).toEqual(prevState);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ describe('DashboardScene', () => {
|
|||||||
|
|
||||||
expect(scene.state.isDirty).toBe(true);
|
expect(scene.state.isDirty).toBe(true);
|
||||||
|
|
||||||
scene.onDiscard();
|
scene.exitEditMode({ skipConfirm: true });
|
||||||
expect(sceneGraph.getTimeRange(scene)!.state.timeZone).toBe(prevState);
|
expect(sceneGraph.getTimeRange(scene)!.state.timeZone).toBe(prevState);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,6 +26,7 @@ import { DashboardModel } from 'app/features/dashboard/state';
|
|||||||
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
|
||||||
import { VariablesChanged } from 'app/features/variables/types';
|
import { VariablesChanged } from 'app/features/variables/types';
|
||||||
import { DashboardDTO, DashboardMeta, SaveDashboardResponseDTO } from 'app/types';
|
import { DashboardDTO, DashboardMeta, SaveDashboardResponseDTO } from 'app/types';
|
||||||
|
import { ShowConfirmModalEvent } from 'app/types/events';
|
||||||
|
|
||||||
import { PanelEditor } from '../panel-edit/PanelEditor';
|
import { PanelEditor } from '../panel-edit/PanelEditor';
|
||||||
import { SaveDashboardDrawer } from '../saving/SaveDashboardDrawer';
|
import { SaveDashboardDrawer } from '../saving/SaveDashboardDrawer';
|
||||||
@ -213,12 +214,29 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDiscard = () => {
|
public exitEditMode({ skipConfirm }: { skipConfirm: boolean }) {
|
||||||
if (!this.canDiscard()) {
|
if (!this.canDiscard()) {
|
||||||
console.error('Trying to discard back to a state that does not exist, initialState undefined');
|
console.error('Trying to discard back to a state that does not exist, initialState undefined');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.state.isDirty || skipConfirm) {
|
||||||
|
this.exitEditModeConfirmed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
appEvents.publish(
|
||||||
|
new ShowConfirmModalEvent({
|
||||||
|
title: 'Discard changes to dashboard?',
|
||||||
|
text: `You have unsaved changes to this dashboard. Are you sure you want to discard them?`,
|
||||||
|
icon: 'trash-alt',
|
||||||
|
yesText: 'Discard',
|
||||||
|
onConfirm: this.exitEditModeConfirmed.bind(this),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private exitEditModeConfirmed() {
|
||||||
// No need to listen to changes anymore
|
// No need to listen to changes anymore
|
||||||
this.stopTrackingChanges();
|
this.stopTrackingChanges();
|
||||||
// Stop url sync before updating url
|
// Stop url sync before updating url
|
||||||
@ -244,7 +262,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
this.startUrlSync();
|
this.startUrlSync();
|
||||||
// Disable grid dragging
|
// Disable grid dragging
|
||||||
this.propagateEditModeChange();
|
this.propagateEditModeChange();
|
||||||
};
|
}
|
||||||
|
|
||||||
public canDiscard() {
|
public canDiscard() {
|
||||||
return this._initialState !== undefined;
|
return this._initialState !== undefined;
|
||||||
@ -266,7 +284,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
newState.version = versionRsp.version;
|
newState.version = versionRsp.version;
|
||||||
|
|
||||||
this._initialState = newState;
|
this._initialState = newState;
|
||||||
this.onDiscard();
|
this.exitEditMode({ skipConfirm: false });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -20,6 +20,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
|||||||
const pageNav = model.getPageNav(location, navIndex);
|
const pageNav = model.getPageNav(location, navIndex);
|
||||||
const bodyToRender = model.getBodyToRender();
|
const bodyToRender = model.getBodyToRender();
|
||||||
const navModel = getNavModel(navIndex, 'dashboards/browse');
|
const navModel = getNavModel(navIndex, 'dashboards/browse');
|
||||||
|
const showDebugger = location.search.includes('scene-debugger');
|
||||||
|
|
||||||
if (editview) {
|
if (editview) {
|
||||||
return (
|
return (
|
||||||
@ -43,7 +44,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
|||||||
{controls.map((control) => (
|
{controls.map((control) => (
|
||||||
<control.Component key={control.state.key} model={control} />
|
<control.Component key={control.state.key} model={control} />
|
||||||
))}
|
))}
|
||||||
<SceneDebugger scene={model} key={'scene-debugger'} />
|
{showDebugger && <SceneDebugger scene={model} key={'scene-debugger'} />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={cx(styles.body)}>
|
<div className={cx(styles.body)}>
|
||||||
@ -83,7 +84,7 @@ function getStyles(theme: GrafanaTheme2) {
|
|||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
background: theme.colors.background.canvas,
|
background: theme.colors.background.canvas,
|
||||||
zIndex: theme.zIndex.navbarFixed,
|
zIndex: theme.zIndex.activePanel,
|
||||||
padding: theme.spacing(2, 0),
|
padding: theme.spacing(2, 0),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
import { screen, render } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import React from 'react';
|
||||||
|
import { TestProvider } from 'test/helpers/TestProvider';
|
||||||
|
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||||
|
|
||||||
|
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||||
|
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
||||||
|
|
||||||
|
import { ToolbarActions } from './NavToolbarActions';
|
||||||
|
|
||||||
|
describe('NavToolbarActions', () => {
|
||||||
|
describe('Give an already saved dashboard', () => {
|
||||||
|
it('Should show correct buttons when not in editing', async () => {
|
||||||
|
setup();
|
||||||
|
|
||||||
|
expect(screen.queryByText('Save dashboard')).not.toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('Edit')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('Share')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should show correct buttons when editing', async () => {
|
||||||
|
setup();
|
||||||
|
|
||||||
|
await userEvent.click(await screen.findByText('Edit'));
|
||||||
|
|
||||||
|
expect(await screen.findByText('Save dashboard')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('Exit edit')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Edit')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Share')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should show correct buttons when in settings menu', async () => {
|
||||||
|
setup();
|
||||||
|
|
||||||
|
await userEvent.click(await screen.findByText('Edit'));
|
||||||
|
await userEvent.click(await screen.findByText('Settings'));
|
||||||
|
|
||||||
|
expect(await screen.findByText('Save dashboard')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByText('Back to dashboard')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let cleanUp = () => {};
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
const dashboard = transformSaveModelToScene({
|
||||||
|
dashboard: {
|
||||||
|
title: 'hello',
|
||||||
|
uid: 'my-uid',
|
||||||
|
schemaVersion: 30,
|
||||||
|
panels: [],
|
||||||
|
version: 10,
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
canSave: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear any data layers
|
||||||
|
dashboard.setState({ $data: undefined });
|
||||||
|
|
||||||
|
const initialSaveModel = transformSceneToSaveModel(dashboard);
|
||||||
|
dashboard.setInitialSaveModel(initialSaveModel);
|
||||||
|
|
||||||
|
dashboard.startUrlSync();
|
||||||
|
|
||||||
|
cleanUp();
|
||||||
|
cleanUp = dashboard.activate();
|
||||||
|
|
||||||
|
const context = getGrafanaContextMock();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TestProvider grafanaContext={context}>
|
||||||
|
<ToolbarActions dashboard={dashboard} />
|
||||||
|
</TestProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const actions = context.chrome.state.getValue().actions;
|
||||||
|
|
||||||
|
return { dashboard, actions };
|
||||||
|
}
|
@ -1,11 +1,13 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService } from '@grafana/runtime';
|
||||||
import { Button } from '@grafana/ui';
|
import { Button, ButtonGroup, Dropdown, Icon, Menu, ToolbarButton, ToolbarButtonRow, useStyles2 } from '@grafana/ui';
|
||||||
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
||||||
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator';
|
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator';
|
||||||
|
import { contextSrv } from 'app/core/core';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton';
|
|
||||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
|
|
||||||
import { ShareModal } from '../sharing/ShareModal';
|
import { ShareModal } from '../sharing/ShareModal';
|
||||||
@ -19,164 +21,300 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
|
export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
|
||||||
const { actions = [], isEditing, viewPanelScene, isDirty, uid, meta, editview } = dashboard.useState();
|
const actions = (
|
||||||
const toolbarActions = (actions ?? []).map((action) => <action.Component key={action.state.key} model={action} />);
|
<ToolbarButtonRow alignment="right">
|
||||||
const rightToolbarActions: JSX.Element[] = [];
|
<ToolbarActions dashboard={dashboard} />
|
||||||
const _legacyDashboardModel = getDashboardSrv().getCurrent();
|
</ToolbarButtonRow>
|
||||||
|
);
|
||||||
|
|
||||||
if (uid && !editview) {
|
return <AppChromeUpdate actions={actions} />;
|
||||||
if (meta.canStar) {
|
});
|
||||||
|
|
||||||
|
NavToolbarActions.displayName = 'NavToolbarActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This part is split into a separate componet to help test this
|
||||||
|
*/
|
||||||
|
export function ToolbarActions({ dashboard }: Props) {
|
||||||
|
const { isEditing, viewPanelScene, isDirty, uid, meta, editview } = dashboard.useState();
|
||||||
|
const canSaveAs = contextSrv.hasEditPermissionInFolders;
|
||||||
|
const toolbarActions: ToolbarAction[] = [];
|
||||||
|
const buttonWithExtraMargin = useStyles2(getStyles);
|
||||||
|
|
||||||
|
toolbarActions.push({
|
||||||
|
group: 'icon-actions',
|
||||||
|
condition: uid && !editview && Boolean(meta.canStar),
|
||||||
|
render: () => {
|
||||||
let desc = meta.isStarred
|
let desc = meta.isStarred
|
||||||
? t('dashboard.toolbar.unmark-favorite', 'Unmark as favorite')
|
? t('dashboard.toolbar.unmark-favorite', 'Unmark as favorite')
|
||||||
: t('dashboard.toolbar.mark-favorite', 'Mark as favorite');
|
: t('dashboard.toolbar.mark-favorite', 'Mark as favorite');
|
||||||
|
return (
|
||||||
toolbarActions.push(
|
<ToolbarButton
|
||||||
<DashNavButton
|
|
||||||
key="star-dashboard-button"
|
|
||||||
tooltip={desc}
|
tooltip={desc}
|
||||||
icon={meta.isStarred ? 'favorite' : 'star'}
|
icon={
|
||||||
iconType={meta.isStarred ? 'mono' : 'default'}
|
<Icon name={meta.isStarred ? 'favorite' : 'star'} size="lg" type={meta.isStarred ? 'mono' : 'default'} />
|
||||||
iconSize="lg"
|
}
|
||||||
|
key="star-dashboard-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
DashboardInteractions.toolbarFavoritesClick();
|
DashboardInteractions.toolbarFavoritesClick();
|
||||||
dashboard.onStarDashboard();
|
dashboard.onStarDashboard();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
toolbarActions.push(
|
});
|
||||||
<DashNavButton
|
|
||||||
key="share-dashboard-button"
|
|
||||||
tooltip={t('dashboard.toolbar.share', 'Share dashboard')}
|
|
||||||
icon="share-alt"
|
|
||||||
iconSize="lg"
|
|
||||||
onClick={() => {
|
|
||||||
DashboardInteractions.toolbarShareClick();
|
|
||||||
dashboard.showModal(new ShareModal({ dashboardRef: dashboard.getRef() }));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
toolbarActions.push(
|
toolbarActions.push({
|
||||||
<DashNavButton
|
group: 'icon-actions',
|
||||||
|
condition: uid && !editview,
|
||||||
|
render: () => (
|
||||||
|
<ToolbarButton
|
||||||
key="view-in-old-dashboard-button"
|
key="view-in-old-dashboard-button"
|
||||||
tooltip={'View as dashboard'}
|
tooltip={'Switch to old dashboard page'}
|
||||||
icon="apps"
|
icon="apps"
|
||||||
onClick={() => locationService.push(`/d/${uid}`)}
|
onClick={() => locationService.push(`/d/${uid}`)}
|
||||||
/>
|
/>
|
||||||
);
|
),
|
||||||
if (dynamicDashNavActions.left.length > 0) {
|
});
|
||||||
dynamicDashNavActions.left.map((action, index) => {
|
|
||||||
|
if (dynamicDashNavActions.left.length > 0) {
|
||||||
|
dynamicDashNavActions.left.map((action, index) => {
|
||||||
|
const props = { dashboard: getDashboardSrv().getCurrent()! };
|
||||||
|
if (action.show(props)) {
|
||||||
const Component = action.component;
|
const Component = action.component;
|
||||||
const element = <Component dashboard={_legacyDashboardModel} />;
|
toolbarActions.push({
|
||||||
typeof action.index === 'number'
|
group: 'icon-actions',
|
||||||
? toolbarActions.splice(action.index, 0, element)
|
condition: true,
|
||||||
: toolbarActions.push(element);
|
render: () => <Component {...props} />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
toolbarActions.push(<NavToolbarSeparator leftActionsSeparator key="separator" />);
|
|
||||||
|
|
||||||
if (dynamicDashNavActions.right.length > 0) {
|
|
||||||
dynamicDashNavActions.right.map((action, index) => {
|
|
||||||
const Component = action.component;
|
|
||||||
const element = <Component dashboard={_legacyDashboardModel} key={`button-custom-${index}`} />;
|
|
||||||
typeof action.index === 'number'
|
|
||||||
? rightToolbarActions.splice(action.index, 0, element)
|
|
||||||
: rightToolbarActions.push(element);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
toolbarActions.push(...rightToolbarActions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewPanelScene) {
|
toolbarActions.push({
|
||||||
toolbarActions.push(
|
group: 'back-button',
|
||||||
|
condition: Boolean(viewPanelScene),
|
||||||
|
render: () => (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
locationService.partial({ viewPanel: null });
|
locationService.partial({ viewPanel: null });
|
||||||
}}
|
}}
|
||||||
tooltip=""
|
tooltip=""
|
||||||
key="back"
|
key="back"
|
||||||
variant="primary"
|
variant="secondary"
|
||||||
fill="text"
|
size="sm"
|
||||||
|
icon="arrow-left"
|
||||||
>
|
>
|
||||||
Back to dashboard
|
Back to dashboard
|
||||||
</Button>
|
</Button>
|
||||||
);
|
),
|
||||||
|
});
|
||||||
|
|
||||||
return <AppChromeUpdate actions={toolbarActions} />;
|
toolbarActions.push({
|
||||||
}
|
group: 'back-button',
|
||||||
|
condition: Boolean(editview),
|
||||||
|
render: () => (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
locationService.partial({ editview: null });
|
||||||
|
}}
|
||||||
|
tooltip=""
|
||||||
|
key="back"
|
||||||
|
fill="text"
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
icon="arrow-left"
|
||||||
|
>
|
||||||
|
Back to dashboard
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
if (!isEditing) {
|
toolbarActions.push({
|
||||||
if (dashboard.canEditDashboard()) {
|
group: 'main-buttons',
|
||||||
toolbarActions.push(
|
condition: uid && !isEditing,
|
||||||
<Button
|
render: () => (
|
||||||
onClick={() => {
|
<Button
|
||||||
dashboard.onEnterEditMode();
|
key="share-dashboard-button"
|
||||||
}}
|
tooltip={t('dashboard.toolbar.share', 'Share dashboard')}
|
||||||
tooltip="Enter edit mode"
|
size="sm"
|
||||||
key="edit"
|
className={buttonWithExtraMargin}
|
||||||
variant="primary"
|
fill="outline"
|
||||||
icon="pen"
|
onClick={() => {
|
||||||
fill="text"
|
DashboardInteractions.toolbarShareClick();
|
||||||
size="sm"
|
dashboard.showModal(new ShareModal({ dashboardRef: dashboard.getRef() }));
|
||||||
>
|
}}
|
||||||
Edit
|
>
|
||||||
</Button>
|
Share
|
||||||
);
|
</Button>
|
||||||
}
|
),
|
||||||
} else {
|
});
|
||||||
if (dashboard.canEditDashboard()) {
|
|
||||||
if (!dashboard.state.meta.isNew) {
|
toolbarActions.push({
|
||||||
toolbarActions.push(
|
group: 'main-buttons',
|
||||||
|
condition: !isEditing && dashboard.canEditDashboard() && !viewPanelScene,
|
||||||
|
render: () => (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
dashboard.onEnterEditMode();
|
||||||
|
}}
|
||||||
|
tooltip="Enter edit mode"
|
||||||
|
key="edit"
|
||||||
|
className={buttonWithExtraMargin}
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
toolbarActions.push({
|
||||||
|
group: 'settings',
|
||||||
|
condition: isEditing && dashboard.canEditDashboard() && !viewPanelScene && !editview,
|
||||||
|
render: () => (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
dashboard.onOpenSettings();
|
||||||
|
}}
|
||||||
|
tooltip="Dashboard settings"
|
||||||
|
fill="text"
|
||||||
|
size="sm"
|
||||||
|
key="settings"
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
toolbarActions.push({
|
||||||
|
group: 'main-buttons',
|
||||||
|
condition: isEditing && !editview && !meta.isNew,
|
||||||
|
render: () => (
|
||||||
|
<Button
|
||||||
|
onClick={() => dashboard.exitEditMode({ skipConfirm: false })}
|
||||||
|
tooltip="Exits edit mode and discards unsaved changes"
|
||||||
|
size="sm"
|
||||||
|
key="discard"
|
||||||
|
fill="text"
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
Exit edit
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
toolbarActions.push({
|
||||||
|
group: 'main-buttons',
|
||||||
|
condition: isEditing && (meta.canSave || canSaveAs),
|
||||||
|
render: () => {
|
||||||
|
// if we only can save
|
||||||
|
if (meta.isNew) {
|
||||||
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
DashboardInteractions.toolbarSaveClick();
|
||||||
|
dashboard.openSaveDrawer({});
|
||||||
|
}}
|
||||||
|
className={buttonWithExtraMargin}
|
||||||
|
tooltip="Save changes"
|
||||||
|
key="save"
|
||||||
|
size="sm"
|
||||||
|
variant={'primary'}
|
||||||
|
>
|
||||||
|
Save dashboard
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we only can save as copy
|
||||||
|
if (canSaveAs && !meta.canSave) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
DashboardInteractions.toolbarSaveClick();
|
||||||
dashboard.openSaveDrawer({ saveAsCopy: true });
|
dashboard.openSaveDrawer({ saveAsCopy: true });
|
||||||
}}
|
}}
|
||||||
size="sm"
|
className={buttonWithExtraMargin}
|
||||||
tooltip="Save as copy"
|
tooltip="Save as copy"
|
||||||
fill="text"
|
key="save"
|
||||||
key="save-as"
|
size="sm"
|
||||||
|
variant={isDirty ? 'primary' : 'secondary'}
|
||||||
>
|
>
|
||||||
Save as
|
Save as copy
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (dashboard.canDiscard()) {
|
|
||||||
toolbarActions.push(
|
// If we can do both save and save as copy we show a button group with dropdown menu
|
||||||
|
const menu = (
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item
|
||||||
|
label="Save"
|
||||||
|
icon="save"
|
||||||
|
onClick={() => {
|
||||||
|
DashboardInteractions.toolbarSaveClick();
|
||||||
|
dashboard.openSaveDrawer({});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Menu.Item
|
||||||
|
label="Save as copy"
|
||||||
|
icon="copy"
|
||||||
|
onClick={() => {
|
||||||
|
DashboardInteractions.toolbarSaveAsClick();
|
||||||
|
dashboard.openSaveDrawer({ saveAsCopy: true });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonGroup className={buttonWithExtraMargin} key="save">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dashboard.onDiscard();
|
DashboardInteractions.toolbarSaveClick();
|
||||||
|
dashboard.openSaveDrawer({});
|
||||||
}}
|
}}
|
||||||
tooltip="Discard changes"
|
tooltip="Save changes"
|
||||||
fill="text"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
key="discard"
|
variant={isDirty ? 'primary' : 'secondary'}
|
||||||
variant="destructive"
|
|
||||||
>
|
>
|
||||||
Discard
|
Save dashboard
|
||||||
</Button>
|
</Button>
|
||||||
);
|
<Dropdown overlay={menu}>
|
||||||
}
|
<Button icon="angle-down" variant={isDirty ? 'primary' : 'secondary'} size="sm" />
|
||||||
toolbarActions.push(
|
</Dropdown>
|
||||||
<Button
|
</ButtonGroup>
|
||||||
onClick={() => {
|
|
||||||
DashboardInteractions.toolbarSaveClick();
|
|
||||||
dashboard.openSaveDrawer({});
|
|
||||||
}}
|
|
||||||
tooltip="Save changes"
|
|
||||||
key="save"
|
|
||||||
size="sm"
|
|
||||||
variant={isDirty ? 'primary' : 'secondary'}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionElements: React.ReactNode[] = [];
|
||||||
|
let lastGroup = '';
|
||||||
|
|
||||||
|
for (const action of toolbarActions) {
|
||||||
|
if (!action.condition) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastGroup && lastGroup !== action.group) {
|
||||||
|
lastGroup && actionElements.push(<NavToolbarSeparator key={`${action.group}-separator`} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
actionElements.push(action.render());
|
||||||
|
lastGroup = action.group;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <AppChromeUpdate actions={toolbarActions} />;
|
return actionElements;
|
||||||
});
|
}
|
||||||
|
|
||||||
NavToolbarActions.displayName = 'NavToolbarActions';
|
interface ToolbarAction {
|
||||||
|
group: string;
|
||||||
|
condition?: boolean | string;
|
||||||
|
render: () => React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyles(theme: GrafanaTheme2) {
|
||||||
|
return css({ margin: theme.spacing(0, 0.5) });
|
||||||
|
}
|
||||||
|
@ -135,7 +135,9 @@ export const DashboardInteractions = {
|
|||||||
toolbarSaveClick: () => {
|
toolbarSaveClick: () => {
|
||||||
reportDashboardInteraction('toolbar_actions_clicked', { item: 'save' });
|
reportDashboardInteraction('toolbar_actions_clicked', { item: 'save' });
|
||||||
},
|
},
|
||||||
|
toolbarSaveAsClick: () => {
|
||||||
|
reportDashboardInteraction('toolbar_actions_clicked', { item: 'save_as' });
|
||||||
|
},
|
||||||
toolbarAddClick: () => {
|
toolbarAddClick: () => {
|
||||||
reportDashboardInteraction('toolbar_actions_clicked', { item: 'add' });
|
reportDashboardInteraction('toolbar_actions_clicked', { item: 'add' });
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user