mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardScene: Basics stubs for starting with editviews (dashboard settings) (#78209)
This commit is contained in:
parent
97c8b2d192
commit
d70d939294
@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SceneObjectState, SceneObject, SceneObjectBase, SceneComponentProps } from '@grafana/scenes';
|
||||
import { Box, Stack } from '@grafana/ui';
|
||||
import { Box, Stack, ToolbarButton } from '@grafana/ui';
|
||||
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { DashboardLinksControls } from './DashboardLinksControls';
|
||||
|
||||
@ -15,7 +17,9 @@ export class DashboardControls extends SceneObjectBase<DashboardControlsState> {
|
||||
}
|
||||
|
||||
function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardControls>) {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const { variableControls, linkControls, timeControls } = model.useState();
|
||||
const { isEditing } = dashboard.useState();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@ -33,6 +37,9 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
|
||||
<linkControls.Component model={linkControls} />
|
||||
</Stack>
|
||||
<Stack justifyContent={'flex-end'}>
|
||||
{isEditing && (
|
||||
<ToolbarButton variant="canvas" icon="cog" tooltip="Dashboard settings" onClick={dashboard.onOpenSettings} />
|
||||
)}
|
||||
{timeControls.map((c) => (
|
||||
<c.Component model={c} key={c.state.key} />
|
||||
))}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as H from 'history';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
|
||||
import { CoreApp, DataQueryRequest, NavIndex, NavModelItem, UrlQueryMap } from '@grafana/data';
|
||||
import { CoreApp, DataQueryRequest, NavIndex, NavModelItem } from '@grafana/data';
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
import {
|
||||
getUrlSyncManager,
|
||||
@ -25,6 +25,7 @@ import { DashboardMeta } from 'app/types';
|
||||
|
||||
import { DashboardSceneRenderer } from '../scene/DashboardSceneRenderer';
|
||||
import { SaveDashboardDrawer } from '../serialization/SaveDashboardDrawer';
|
||||
import { DashboardEditView } from '../settings/utils';
|
||||
import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper';
|
||||
import { getDashboardUrl } from '../utils/urlBuilders';
|
||||
import { findVizPanelByKey, forceRenderChildren, getClosestVizPanel, getPanelIdForVizPanel } from '../utils/utils';
|
||||
@ -55,6 +56,8 @@ export interface DashboardSceneState extends SceneObjectState {
|
||||
inspectPanelKey?: string;
|
||||
/** Panel to view in full screen */
|
||||
viewPanelKey?: string;
|
||||
/** Edit view */
|
||||
editview?: DashboardEditView;
|
||||
/** Scene object that handles the current drawer or modal */
|
||||
overlay?: SceneObject;
|
||||
}
|
||||
@ -78,7 +81,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
/**
|
||||
* Url state before editing started
|
||||
*/
|
||||
private _initiallUrlState?: UrlQueryMap;
|
||||
private _initialUrlState?: H.Location;
|
||||
/**
|
||||
* change tracking subscription
|
||||
*/
|
||||
@ -129,7 +132,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
public onEnterEditMode = () => {
|
||||
// Save this state
|
||||
this._initialState = sceneUtils.cloneSceneObjectState(this.state);
|
||||
this._initiallUrlState = locationService.getSearchObject();
|
||||
this._initialUrlState = locationService.getLocation();
|
||||
|
||||
// Switch to edit mode
|
||||
this.setState({ isEditing: true });
|
||||
@ -149,7 +152,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
// Stop url sync before updating url
|
||||
this.stopUrlSync();
|
||||
// Now we can update url
|
||||
locationService.partial(this._initiallUrlState!, true);
|
||||
locationService.replace({ pathname: this._initialUrlState?.pathname, search: this._initialUrlState?.search });
|
||||
// Update state and disable editing
|
||||
this.setState({ ...this._initialState, isEditing: false });
|
||||
// and start url sync again
|
||||
@ -167,14 +170,14 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
};
|
||||
|
||||
public getPageNav(location: H.Location, navIndex: NavIndex) {
|
||||
const { meta } = this.state;
|
||||
const { meta, viewPanelKey } = this.state;
|
||||
|
||||
let pageNav: NavModelItem = {
|
||||
text: this.state.title,
|
||||
url: getDashboardUrl({
|
||||
uid: this.state.uid,
|
||||
currentQueryParams: location.search,
|
||||
updateQuery: { viewPanel: null, inspect: null },
|
||||
updateQuery: { viewPanel: null, inspect: null, editview: null },
|
||||
useExperimentalURL: Boolean(config.featureToggles.dashboardSceneForViewers && meta.canEdit),
|
||||
}),
|
||||
};
|
||||
@ -205,7 +208,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.viewPanelKey) {
|
||||
if (viewPanelKey) {
|
||||
pageNav = {
|
||||
text: 'View panel',
|
||||
parentItem: pageNav,
|
||||
@ -275,6 +278,10 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
}
|
||||
}
|
||||
|
||||
public onOpenSettings = () => {
|
||||
locationService.partial({ editview: 'settings' });
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by the SceneQueryRunner to privide contextural parameters (tracking) props for the request
|
||||
*/
|
||||
|
@ -3,7 +3,6 @@ import React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneDebugger } from '@grafana/scenes';
|
||||
import { CustomScrollbar, useStyles2 } from '@grafana/ui';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
@ -14,19 +13,20 @@ import { DashboardScene } from './DashboardScene';
|
||||
import { NavToolbarActions } from './NavToolbarActions';
|
||||
|
||||
export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardScene>) {
|
||||
const { controls, viewPanelKey: viewPanelId, overlay } = model.useState();
|
||||
const { controls, viewPanelKey, overlay, editview } = model.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
const location = useLocation();
|
||||
const navIndex = useSelector((state) => state.navIndex);
|
||||
const pageNav = model.getPageNav(location, navIndex);
|
||||
const bodyToRender = model.getBodyToRender(viewPanelId);
|
||||
const bodyToRender = model.getBodyToRender(viewPanelKey);
|
||||
const navModel = getNavModel(navIndex, 'dashboards/browse');
|
||||
|
||||
const navProps = config.featureToggles.dashboardSceneForViewers
|
||||
? { navModel: getNavModel(navIndex, 'dashboards/browse') }
|
||||
: { navId: 'scenes' };
|
||||
if (editview) {
|
||||
return <editview.Component model={editview} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page {...navProps} pageNav={pageNav} layout={PageLayoutType.Custom}>
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Custom}>
|
||||
<CustomScrollbar autoHeightMin={'100%'}>
|
||||
<div className={styles.canvasContent}>
|
||||
<NavToolbarActions dashboard={model} />
|
||||
|
@ -6,6 +6,7 @@ import { SceneObjectUrlSyncHandler, SceneObjectUrlValues } from '@grafana/scenes
|
||||
import appEvents from 'app/core/app_events';
|
||||
|
||||
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
|
||||
import { createDashboardEditViewFor } from '../settings/utils';
|
||||
import { findVizPanelByKey } from '../utils/utils';
|
||||
|
||||
import { DashboardScene, DashboardSceneState } from './DashboardScene';
|
||||
@ -17,18 +18,35 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||
constructor(private _scene: DashboardScene) {}
|
||||
|
||||
getKeys(): string[] {
|
||||
return ['inspect', 'viewPanel'];
|
||||
return ['inspect', 'viewPanel', 'editview'];
|
||||
}
|
||||
|
||||
getUrlState(): SceneObjectUrlValues {
|
||||
const state = this._scene.state;
|
||||
return { inspect: state.inspectPanelKey, viewPanel: state.viewPanelKey };
|
||||
return {
|
||||
inspect: state.inspectPanelKey,
|
||||
viewPanel: state.viewPanelKey,
|
||||
editview: state.editview?.getUrlKey(),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromUrl(values: SceneObjectUrlValues): void {
|
||||
const { inspectPanelKey: inspectPanelId, viewPanelKey: viewPanelId } = this._scene.state;
|
||||
const { inspectPanelKey, viewPanelKey, meta, isEditing } = this._scene.state;
|
||||
const update: Partial<DashboardSceneState> = {};
|
||||
|
||||
if (typeof values.editview === 'string' && meta.canEdit) {
|
||||
update.editview = createDashboardEditViewFor(values.editview);
|
||||
|
||||
// If we are not in editing (for example after full page reload)
|
||||
if (!isEditing) {
|
||||
// Not sure what is best to do here.
|
||||
// The reason for the timeout is for this change to happen after the url sync has completed
|
||||
setTimeout(() => this._scene.onEnterEditMode());
|
||||
}
|
||||
} else if (values.hasOwnProperty('editview')) {
|
||||
update.editview = undefined;
|
||||
}
|
||||
|
||||
// Handle inspect object state
|
||||
if (typeof values.inspect === 'string') {
|
||||
const panel = findVizPanelByKey(this._scene, values.inspect);
|
||||
@ -40,7 +58,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||
|
||||
update.inspectPanelKey = values.inspect;
|
||||
update.overlay = new PanelInspectDrawer({ panelRef: panel.getRef() });
|
||||
} else if (inspectPanelId) {
|
||||
} else if (inspectPanelKey) {
|
||||
update.inspectPanelKey = undefined;
|
||||
update.overlay = undefined;
|
||||
}
|
||||
@ -61,7 +79,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||
}
|
||||
|
||||
update.viewPanelKey = values.viewPanel;
|
||||
} else if (viewPanelId) {
|
||||
} else if (viewPanelKey) {
|
||||
update.viewPanelKey = undefined;
|
||||
}
|
||||
|
||||
|
@ -16,10 +16,10 @@ interface Props {
|
||||
}
|
||||
|
||||
export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
|
||||
const { actions = [], isEditing, viewPanelKey, isDirty, uid, meta } = dashboard.useState();
|
||||
const { actions = [], isEditing, viewPanelKey, isDirty, uid, meta, editview } = dashboard.useState();
|
||||
const toolbarActions = (actions ?? []).map((action) => <action.Component key={action.state.key} model={action} />);
|
||||
|
||||
if (uid) {
|
||||
if (uid && !editview) {
|
||||
if (meta.canStar) {
|
||||
let desc = meta.isStarred
|
||||
? t('dashboard.toolbar.unmark-favorite', 'Unmark as favorite')
|
||||
@ -101,7 +101,7 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
|
||||
</Button>
|
||||
);
|
||||
toolbarActions.push(
|
||||
<Button onClick={dashboard.onDiscard} tooltip="Save changes" fill="text" key="discard" variant="destructive">
|
||||
<Button onClick={dashboard.onDiscard} tooltip="Discard changes" fill="text" key="discard" variant="destructive">
|
||||
Discard
|
||||
</Button>
|
||||
);
|
||||
|
@ -79,6 +79,12 @@ export function setupKeyboardShortcuts(scene: DashboardScene) {
|
||||
onTrigger: () => sceneGraph.getTimeRange(scene).onRefresh(),
|
||||
});
|
||||
|
||||
// Dashboard settings
|
||||
keybindings.addBinding({
|
||||
key: 'd s',
|
||||
onTrigger: scene.onOpenSettings,
|
||||
});
|
||||
|
||||
// toggle all panel legends (TODO)
|
||||
// delete panel (TODO when we work on editing)
|
||||
// toggle all exemplars (TODO)
|
||||
|
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PageLayoutType } from '@grafana/data';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { GeneralSettingsEditView } from './GeneralSettings';
|
||||
import { DashboardEditView, useDashboardEditPageNav } from './utils';
|
||||
|
||||
export interface AnnotationsEditViewState extends SceneObjectState {}
|
||||
|
||||
export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewState> implements DashboardEditView {
|
||||
public getUrlKey(): string {
|
||||
return 'annotations';
|
||||
}
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<GeneralSettingsEditView>) => {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
|
||||
return (
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div>Annotations todo</div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PageLayoutType } from '@grafana/data';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { DashboardEditView, useDashboardEditPageNav } from './utils';
|
||||
|
||||
export interface GeneralSettingsEditViewState extends SceneObjectState {}
|
||||
|
||||
export class GeneralSettingsEditView
|
||||
extends SceneObjectBase<GeneralSettingsEditViewState>
|
||||
implements DashboardEditView
|
||||
{
|
||||
public getUrlKey(): string {
|
||||
return 'settings';
|
||||
}
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<GeneralSettingsEditView>) => {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
|
||||
return (
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div>General todo</div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PageLayoutType } from '@grafana/data';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { GeneralSettingsEditView } from './GeneralSettings';
|
||||
import { DashboardEditView, useDashboardEditPageNav } from './utils';
|
||||
|
||||
export interface VariablesEditViewState extends SceneObjectState {}
|
||||
|
||||
export class VariablesEditView extends SceneObjectBase<VariablesEditViewState> implements DashboardEditView {
|
||||
public getUrlKey(): string {
|
||||
return 'variables';
|
||||
}
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<GeneralSettingsEditView>) => {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
|
||||
return (
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div>variables todo</div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
}
|
60
public/app/features/dashboard-scene/settings/utils.ts
Normal file
60
public/app/features/dashboard-scene/settings/utils.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { locationUtil, NavModelItem } from '@grafana/data';
|
||||
import { SceneObject } from '@grafana/scenes';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
|
||||
import { AnnotationsEditView } from './AnnotationsEditView';
|
||||
import { GeneralSettingsEditView } from './GeneralSettings';
|
||||
import { VariablesEditView } from './VariablesEditView';
|
||||
|
||||
export interface DashboardEditView extends SceneObject {
|
||||
getUrlKey(): string;
|
||||
}
|
||||
|
||||
export function useDashboardEditPageNav(dashboard: DashboardScene, currentEditView: string) {
|
||||
const location = useLocation();
|
||||
const navIndex = useSelector((state) => state.navIndex);
|
||||
const navModel = getNavModel(navIndex, 'dashboards/browse');
|
||||
const dashboardPageNav = dashboard.getPageNav(location, navIndex);
|
||||
|
||||
const pageNav: NavModelItem = {
|
||||
text: 'Settings',
|
||||
children: [
|
||||
{
|
||||
text: t('dashboard-settings.general.title', 'General'),
|
||||
url: locationUtil.getUrlForPartial(location, { editview: 'settings', editIndex: null }),
|
||||
active: currentEditView === 'settings',
|
||||
},
|
||||
{
|
||||
text: t('dashboard-settings.annotations.title', 'Annotations'),
|
||||
url: locationUtil.getUrlForPartial(location, { editview: 'annotations', editIndex: null }),
|
||||
active: currentEditView === 'annotations',
|
||||
},
|
||||
{
|
||||
text: t('dashboard-settings.variables.title', 'Variables'),
|
||||
url: locationUtil.getUrlForPartial(location, { editview: 'variables', editIndex: null }),
|
||||
active: currentEditView === 'variables',
|
||||
},
|
||||
],
|
||||
parentItem: dashboardPageNav,
|
||||
};
|
||||
|
||||
return { navModel, pageNav };
|
||||
}
|
||||
|
||||
export function createDashboardEditViewFor(editview: string): DashboardEditView {
|
||||
switch (editview) {
|
||||
case 'annotations':
|
||||
return new AnnotationsEditView({});
|
||||
case 'variables':
|
||||
return new VariablesEditView({});
|
||||
case 'settings':
|
||||
default:
|
||||
return new GeneralSettingsEditView({});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user