The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts

227 lines
8.3 KiB

import { Unsubscribable } from 'rxjs';
import { AppEvents } from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import { SceneObjectUrlSyncHandler, SceneObjectUrlValues, VizPanel } from '@grafana/scenes';
import appEvents from 'app/core/app_events';
import { contextSrv } from 'app/core/core';
import { KioskMode } from 'app/types';
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
import { createDashboardEditViewFor } from '../settings/utils';
import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer';
import { ShareModal } from '../sharing/ShareModal';
import { containsCloneKey } from '../utils/clone';
import { findVizPanelByKey, getLibraryPanelBehavior } from '../utils/utils';
import { DashboardScene, DashboardSceneState } from './DashboardScene';
import { LibraryPanelBehavior } from './LibraryPanelBehavior';
import { ViewPanelScene } from './ViewPanelScene';
import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
import { DashboardRepeatsProcessedEvent } from './types/DashboardRepeatsProcessedEvent';
export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
private _viewEventSub?: Unsubscribable;
private _inspectEventSub?: Unsubscribable;
constructor(private _scene: DashboardScene) {}
getKeys(): string[] {
return ['inspect', 'viewPanel', 'editPanel', 'editview', 'autofitpanels', 'kiosk', 'shareView'];
}
getUrlState(): SceneObjectUrlValues {
const state = this._scene.state;
return {
inspect: state.inspectPanelKey,
autofitpanels: this.getAutoFitPanels(),
viewPanel: state.viewPanelScene?.getUrlKey(),
editview: state.editview?.getUrlKey(),
editPanel: state.editPanel?.getUrlKey() || undefined,
kiosk: state.kioskMode === KioskMode.Full ? '' : undefined,
shareView: state.shareView,
orgId: contextSrv.user.orgId.toString(),
};
}
private getAutoFitPanels(): string | undefined {
if (this._scene.state.body instanceof DefaultGridLayoutManager) {
return this._scene.state.body.state.grid.state.UNSAFE_fitPanels ? 'true' : undefined;
}
return undefined;
}
updateFromUrl(values: SceneObjectUrlValues): void {
const { inspectPanelKey, viewPanelScene, isEditing, editPanel, shareView } = this._scene.state;
const update: Partial<DashboardSceneState> = {};
if (typeof values.editview === 'string' && this._scene.canEditDashboard()) {
update.editview = createDashboardEditViewFor(values.editview);
// If we are not in editing (for example after full page reload)
if (!isEditing) {
if (this._scene.state.editable) {
// 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 {
update.editview = undefined;
}
}
} else if (values.hasOwnProperty('editview')) {
update.editview = undefined;
}
// Handle inspect object state
if (typeof values.inspect === 'string') {
let panel = findVizPanelByKey(this._scene, values.inspect);
if (!panel) {
// If we are trying to view a repeat clone that can't be found it might be that the repeats have not been processed yet
// Here we check if the key contains the clone key so we force the repeat processing
// It doesn't matter if the element or the ancestors are clones or not, just that the key contains the clone key
if (containsCloneKey(values.inspect)) {
this._handleInspectRepeatClone(values.inspect);
return;
}
appEvents.emit(AppEvents.alertError, ['Panel not found']);
locationService.partial({ inspect: null });
return;
}
update.inspectPanelKey = values.inspect;
update.overlay = new PanelInspectDrawer({ panelRef: panel.getRef() });
} else if (inspectPanelKey) {
update.inspectPanelKey = undefined;
update.overlay = undefined;
}
// Handle view panel state
if (typeof values.viewPanel === 'string') {
const panel = findVizPanelByKey(this._scene, values.viewPanel);
if (!panel) {
// If we are trying to view a repeat clone that can't be found it might be that the repeats have not been processed yet
// Here we check if the key contains the clone key so we force the repeat processing
// It doesn't matter if the element or the ancestors are clones or not, just that the key contains the clone key
if (containsCloneKey(values.viewPanel)) {
this._handleViewRepeatClone(values.viewPanel);
return;
}
appEvents.emit(AppEvents.alertError, ['Panel not found']);
locationService.partial({ viewPanel: null });
return;
}
update.viewPanelScene = new ViewPanelScene({ panelRef: panel.getRef() });
} else if (viewPanelScene && values.viewPanel === null) {
update.viewPanelScene = undefined;
}
// Handle edit panel state
if (typeof values.editPanel === 'string') {
const panel = findVizPanelByKey(this._scene, values.editPanel);
if (!panel) {
console.warn(`Panel ${values.editPanel} not found`);
return;
}
// We cannot simultaneously be in edit and view panel state.
if (this._scene.state.viewPanelScene) {
this._scene.setState({ viewPanelScene: undefined });
}
// If we are not in editing (for example after full page reload)
if (!isEditing) {
this._scene.onEnterEditMode();
}
const libPanelBehavior = getLibraryPanelBehavior(panel);
if (libPanelBehavior && !libPanelBehavior?.state.isLoaded) {
this._waitForLibPanelToLoadBeforeEnteringPanelEdit(panel, libPanelBehavior);
return;
}
update.editPanel = buildPanelEditScene(panel);
} else if (editPanel && values.editPanel === null) {
update.editPanel = undefined;
}
if (typeof values.shareView === 'string') {
update.shareView = values.shareView;
update.overlay = config.featureToggles.newDashboardSharingComponent
? new ShareDrawer({
shareView: values.shareView,
})
: new ShareModal({
activeTab: values.shareView,
});
} else if (shareView && values.shareView === null) {
update.overlay = undefined;
update.shareView = undefined;
}
const layout = this._scene.state.body;
if (layout instanceof DefaultGridLayoutManager) {
const UNSAFE_fitPanels = typeof values.autofitpanels === 'string';
if (!!layout.state.grid.state.UNSAFE_fitPanels !== UNSAFE_fitPanels) {
layout.state.grid.setState({ UNSAFE_fitPanels });
}
}
if (typeof values.kiosk === 'string') {
if (values.kiosk === 'true' || values.kiosk === '') {
update.kioskMode = KioskMode.Full;
}
}
if (Object.keys(update).length > 0) {
this._scene.setState(update);
}
}
private _handleInspectRepeatClone(inspect: string) {
if (!this._inspectEventSub) {
this._inspectEventSub = this._scene.subscribeToEvent(DashboardRepeatsProcessedEvent, () => {
const panel = findVizPanelByKey(this._scene, inspect);
if (panel) {
this._inspectEventSub?.unsubscribe();
this._scene.setState({
inspectPanelKey: inspect,
overlay: new PanelInspectDrawer({ panelRef: panel.getRef() }),
});
}
});
}
}
private _handleViewRepeatClone(viewPanel: string) {
if (!this._viewEventSub) {
this._viewEventSub = this._scene.subscribeToEvent(DashboardRepeatsProcessedEvent, () => {
const panel = findVizPanelByKey(this._scene, viewPanel);
if (panel) {
this._viewEventSub?.unsubscribe();
this._scene.setState({ viewPanelScene: new ViewPanelScene({ panelRef: panel.getRef() }) });
}
});
}
}
/**
* Temporary solution, with some refactoring of PanelEditor we can remove this
*/
private _waitForLibPanelToLoadBeforeEnteringPanelEdit(panel: VizPanel, libPanel: LibraryPanelBehavior) {
const sub = libPanel.subscribeToState((state) => {
if (state.isLoaded) {
this._scene.setState({ editPanel: buildPanelEditScene(panel) });
sub.unsubscribe();
}
});
}
}