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/plugins/panel/canvas/CanvasPanel.tsx

344 lines
10 KiB

import { Component } from 'react';
import * as React from 'react';
import { ReplaySubject, Subscription } from 'rxjs';
import { PanelProps } from '@grafana/data';
import { locationService } from '@grafana/runtime/src';
import { PanelContext, PanelContextRoot } from '@grafana/ui';
import { CanvasFrameOptions } from 'app/features/canvas/frame';
import { ElementState } from 'app/features/canvas/runtime/element';
import { Scene } from 'app/features/canvas/runtime/scene';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events';
import { SetBackground } from './components/SetBackground';
import { InlineEdit } from './editor/inline/InlineEdit';
import { Options } from './panelcfg.gen';
import { AnchorPoint, CanvasTooltipPayload, ConnectionState } from './types';
interface Props extends PanelProps<Options> {}
interface State {
refresh: number;
openInlineEdit: boolean;
openSetBackground: boolean;
contextMenuAnchorPoint: AnchorPoint;
moveableAction: boolean;
}
export interface InstanceState {
scene: Scene;
selected: ElementState[];
selectedConnection?: ConnectionState;
}
export interface SelectionAction {
panel: CanvasPanel;
}
let canvasInstances: CanvasPanel[] = [];
let activeCanvasPanel: CanvasPanel | undefined = undefined;
let isInlineEditOpen = false;
let isSetBackgroundOpen = false;
export const activePanelSubject = new ReplaySubject<SelectionAction>(1);
export class CanvasPanel extends Component<Props, State> {
declare context: React.ContextType<typeof PanelContextRoot>;
static contextType = PanelContextRoot;
panelContext: PanelContext | undefined;
readonly scene: Scene;
private subs = new Subscription();
needsReload = false;
isEditing = locationService.getSearchObject().editPanel !== undefined;
constructor(props: Props) {
super(props);
this.state = {
refresh: 0,
openInlineEdit: false,
openSetBackground: false,
contextMenuAnchorPoint: { x: 0, y: 0 },
moveableAction: false,
};
// TODO: Will need to update this approach for dashboard scenes
// migration (new dashboard edit experience)
const dashboard = getDashboardSrv().getCurrent();
const allowEditing = this.props.options.inlineEditing && dashboard?.editable;
// Only the initial options are ever used.
// later changes are all controlled by the scene
this.scene = new Scene(
this.props.options.root,
allowEditing,
this.props.options.showAdvancedTypes,
Canvas: Add Pan and Zoom (#76705) * Canvas: Add Zoom * Scale selecto components based on zoom state * Fix pan by reverting to 3.1.0 for zoom-pan * Update to latest library that fixes pan regression * Add mini map to canvas pan zoom * Fix selecto and anchors on hover * Update naming to be more clear * Switch back to contentComponent * Apply transformScale to drag and resize * Update connection source and target scaling * Add option to display mini map * Update yarn lock * Revert "Update yarn lock" This reverts commit 3d1dd65d5726fb0fd0813347451884a4034ae5d3. * Set yarn lock to main * Revert "Set yarn lock to main" This reverts commit 64bc50557e75657fae14f81077d1d08b4e9e9029. * Update to Yarn 4 * Add react-zoom-pan-pinch * Update react-zoom-pan checksum * Revert changes to json files * Remove last line of api merged * Remove last lines of all impacted jsons * Update home json * Update coordinate calc function to include scale * Fix types in coordinate calc function * Fix util calculation for transform * Fix arrow anchor shift behavior * Fix scale offset when adding elements during zoom * Fix drag of selected group during zoom * Add feature flag for canvas pan zoom * Revert "Add feature flag for canvas pan zoom" This reverts commit b026e31d8d9ed64b1fe307f852df10292fffadf4. * Regenerate feature flag after merge * Apply feature flag to enable pan zoom wrappers * Add mini map toggle behind feature flag * Simplify minimap behavior * Update feature flag registry * Set minimap to false by default * fix gen-cue * Set toggles gen to main Add blank line to toggle gen csv * Add canvas pan zoom to csv * Remove old comment * Change ref parameter to be more descriptive * Rename visibleFun to be more descriptive * Consolidate transformScale transformRef in util * Remove non-null assertion on connection parentRect * Consolidate parentRect null coalescing into object * Remove minimap and change toggle * Add controls inline help for pan and zoom * Clean up mouse events * Pull scale out of ref and isolate transform * Remove transform ref from scene div * Fix context menu visible behavior * Fix connections and update util functions * Move transform component instance to util * fix backend test * minor updates * Clean up connections / fix minor bug where offset of arrow wasn't being calculated correctly * missed connection code cleanup * cleanup scene code a bit more * actually fix backend test * move eslint disable line closer to actual issue --------- Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
1 year ago
this.props.options.panZoom,
this.props.options.infinitePan,
this.onUpdateScene,
this
);
this.scene.updateSize(props.width, props.height);
this.scene.updateData(props.data);
this.scene.inlineEditingCallback = this.openInlineEdit;
this.scene.setBackgroundCallback = this.openSetBackground;
this.scene.tooltipCallback = this.tooltipCallback;
this.scene.moveableActionCallback = this.moveableActionCallback;
this.subs.add(
this.props.eventBus.subscribe(PanelEditEnteredEvent, (evt: PanelEditEnteredEvent) => {
// Remove current selection when entering edit mode for any panel in dashboard
this.scene.clearCurrentSelection();
this.closeInlineEdit();
})
);
this.subs.add(
this.props.eventBus.subscribe(PanelEditExitedEvent, (evt: PanelEditExitedEvent) => {
if (this.props.id === evt.payload) {
this.needsReload = true;
this.scene.clearCurrentSelection();
}
})
);
}
componentDidMount() {
activeCanvasPanel = this;
activePanelSubject.next({ panel: this });
this.panelContext = this.context;
if (this.panelContext.onInstanceStateChange) {
this.panelContext.onInstanceStateChange({
scene: this.scene,
layer: this.scene.root,
});
this.subs.add(
this.scene.selection.subscribe({
next: (v) => {
if (v.length) {
activeCanvasPanel = this;
activePanelSubject.next({ panel: this });
}
canvasInstances.forEach((canvasInstance) => {
if (canvasInstance !== activeCanvasPanel) {
canvasInstance.scene.clearCurrentSelection(true);
canvasInstance.scene.connections.select(undefined);
}
});
this.panelContext?.onInstanceStateChange!({
scene: this.scene,
selected: v,
layer: this.scene.root,
});
},
})
);
this.subs.add(
this.scene.connections.selection.subscribe({
next: (v) => {
if (!this.context.instanceState) {
return;
}
this.panelContext?.onInstanceStateChange!({
scene: this.scene,
selected: this.context.instanceState.selected,
selectedConnection: v,
layer: this.scene.root,
});
if (v) {
activeCanvasPanel = this;
activePanelSubject.next({ panel: this });
}
canvasInstances.forEach((canvasInstance) => {
if (canvasInstance !== activeCanvasPanel) {
canvasInstance.scene.clearCurrentSelection(true);
canvasInstance.scene.connections.select(undefined);
}
});
setTimeout(() => {
this.forceUpdate();
});
},
})
);
}
canvasInstances.push(this);
}
componentWillUnmount() {
this.scene.subscription.unsubscribe();
this.subs.unsubscribe();
isInlineEditOpen = false;
isSetBackgroundOpen = false;
canvasInstances = canvasInstances.filter((ci) => ci.props.id !== activeCanvasPanel?.props.id);
}
// NOTE, all changes to the scene flow through this function
// even the editor gets current state from the same scene instance!
onUpdateScene = (root: CanvasFrameOptions) => {
const { onOptionsChange, options } = this.props;
onOptionsChange({
...options,
root,
});
this.setState({ refresh: this.state.refresh + 1 });
activePanelSubject.next({ panel: this });
};
shouldComponentUpdate(nextProps: Props, nextState: State) {
const { width, height, data, options } = this.props;
let changed = false;
if (width !== nextProps.width || height !== nextProps.height) {
this.scene.updateSize(nextProps.width, nextProps.height);
changed = true;
}
if (data !== nextProps.data && !this.scene.ignoreDataUpdate) {
this.scene.updateData(nextProps.data);
changed = true;
}
if (options !== nextProps.options && !this.scene.ignoreDataUpdate) {
this.scene.updateData(nextProps.data);
changed = true;
}
if (this.state.refresh !== nextState.refresh) {
changed = true;
}
if (this.state.openInlineEdit !== nextState.openInlineEdit) {
changed = true;
}
if (this.state.openSetBackground !== nextState.openSetBackground) {
changed = true;
}
if (this.state.moveableAction !== nextState.moveableAction) {
changed = true;
}
// After editing, the options are valid, but the scene was in a different panel or inline editing mode has changed
const inlineEditingSwitched = this.props.options.inlineEditing !== nextProps.options.inlineEditing;
const shouldShowAdvancedTypesSwitched =
this.props.options.showAdvancedTypes !== nextProps.options.showAdvancedTypes;
Canvas: Add Pan and Zoom (#76705) * Canvas: Add Zoom * Scale selecto components based on zoom state * Fix pan by reverting to 3.1.0 for zoom-pan * Update to latest library that fixes pan regression * Add mini map to canvas pan zoom * Fix selecto and anchors on hover * Update naming to be more clear * Switch back to contentComponent * Apply transformScale to drag and resize * Update connection source and target scaling * Add option to display mini map * Update yarn lock * Revert "Update yarn lock" This reverts commit 3d1dd65d5726fb0fd0813347451884a4034ae5d3. * Set yarn lock to main * Revert "Set yarn lock to main" This reverts commit 64bc50557e75657fae14f81077d1d08b4e9e9029. * Update to Yarn 4 * Add react-zoom-pan-pinch * Update react-zoom-pan checksum * Revert changes to json files * Remove last line of api merged * Remove last lines of all impacted jsons * Update home json * Update coordinate calc function to include scale * Fix types in coordinate calc function * Fix util calculation for transform * Fix arrow anchor shift behavior * Fix scale offset when adding elements during zoom * Fix drag of selected group during zoom * Add feature flag for canvas pan zoom * Revert "Add feature flag for canvas pan zoom" This reverts commit b026e31d8d9ed64b1fe307f852df10292fffadf4. * Regenerate feature flag after merge * Apply feature flag to enable pan zoom wrappers * Add mini map toggle behind feature flag * Simplify minimap behavior * Update feature flag registry * Set minimap to false by default * fix gen-cue * Set toggles gen to main Add blank line to toggle gen csv * Add canvas pan zoom to csv * Remove old comment * Change ref parameter to be more descriptive * Rename visibleFun to be more descriptive * Consolidate transformScale transformRef in util * Remove non-null assertion on connection parentRect * Consolidate parentRect null coalescing into object * Remove minimap and change toggle * Add controls inline help for pan and zoom * Clean up mouse events * Pull scale out of ref and isolate transform * Remove transform ref from scene div * Fix context menu visible behavior * Fix connections and update util functions * Move transform component instance to util * fix backend test * minor updates * Clean up connections / fix minor bug where offset of arrow wasn't being calculated correctly * missed connection code cleanup * cleanup scene code a bit more * actually fix backend test * move eslint disable line closer to actual issue --------- Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
1 year ago
const panZoomSwitched = this.props.options.panZoom !== nextProps.options.panZoom;
const infinitePanSwitched = this.props.options.infinitePan !== nextProps.options.infinitePan;
if (
this.needsReload ||
inlineEditingSwitched ||
shouldShowAdvancedTypesSwitched ||
panZoomSwitched ||
infinitePanSwitched
) {
if (inlineEditingSwitched) {
// Replace scene div to prevent selecto instance leaks
this.scene.revId++;
}
this.needsReload = false;
Canvas: Add Pan and Zoom (#76705) * Canvas: Add Zoom * Scale selecto components based on zoom state * Fix pan by reverting to 3.1.0 for zoom-pan * Update to latest library that fixes pan regression * Add mini map to canvas pan zoom * Fix selecto and anchors on hover * Update naming to be more clear * Switch back to contentComponent * Apply transformScale to drag and resize * Update connection source and target scaling * Add option to display mini map * Update yarn lock * Revert "Update yarn lock" This reverts commit 3d1dd65d5726fb0fd0813347451884a4034ae5d3. * Set yarn lock to main * Revert "Set yarn lock to main" This reverts commit 64bc50557e75657fae14f81077d1d08b4e9e9029. * Update to Yarn 4 * Add react-zoom-pan-pinch * Update react-zoom-pan checksum * Revert changes to json files * Remove last line of api merged * Remove last lines of all impacted jsons * Update home json * Update coordinate calc function to include scale * Fix types in coordinate calc function * Fix util calculation for transform * Fix arrow anchor shift behavior * Fix scale offset when adding elements during zoom * Fix drag of selected group during zoom * Add feature flag for canvas pan zoom * Revert "Add feature flag for canvas pan zoom" This reverts commit b026e31d8d9ed64b1fe307f852df10292fffadf4. * Regenerate feature flag after merge * Apply feature flag to enable pan zoom wrappers * Add mini map toggle behind feature flag * Simplify minimap behavior * Update feature flag registry * Set minimap to false by default * fix gen-cue * Set toggles gen to main Add blank line to toggle gen csv * Add canvas pan zoom to csv * Remove old comment * Change ref parameter to be more descriptive * Rename visibleFun to be more descriptive * Consolidate transformScale transformRef in util * Remove non-null assertion on connection parentRect * Consolidate parentRect null coalescing into object * Remove minimap and change toggle * Add controls inline help for pan and zoom * Clean up mouse events * Pull scale out of ref and isolate transform * Remove transform ref from scene div * Fix context menu visible behavior * Fix connections and update util functions * Move transform component instance to util * fix backend test * minor updates * Clean up connections / fix minor bug where offset of arrow wasn't being calculated correctly * missed connection code cleanup * cleanup scene code a bit more * actually fix backend test * move eslint disable line closer to actual issue --------- Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
1 year ago
this.scene.load(
nextProps.options.root,
nextProps.options.inlineEditing,
nextProps.options.showAdvancedTypes,
nextProps.options.panZoom,
nextProps.options.infinitePan
Canvas: Add Pan and Zoom (#76705) * Canvas: Add Zoom * Scale selecto components based on zoom state * Fix pan by reverting to 3.1.0 for zoom-pan * Update to latest library that fixes pan regression * Add mini map to canvas pan zoom * Fix selecto and anchors on hover * Update naming to be more clear * Switch back to contentComponent * Apply transformScale to drag and resize * Update connection source and target scaling * Add option to display mini map * Update yarn lock * Revert "Update yarn lock" This reverts commit 3d1dd65d5726fb0fd0813347451884a4034ae5d3. * Set yarn lock to main * Revert "Set yarn lock to main" This reverts commit 64bc50557e75657fae14f81077d1d08b4e9e9029. * Update to Yarn 4 * Add react-zoom-pan-pinch * Update react-zoom-pan checksum * Revert changes to json files * Remove last line of api merged * Remove last lines of all impacted jsons * Update home json * Update coordinate calc function to include scale * Fix types in coordinate calc function * Fix util calculation for transform * Fix arrow anchor shift behavior * Fix scale offset when adding elements during zoom * Fix drag of selected group during zoom * Add feature flag for canvas pan zoom * Revert "Add feature flag for canvas pan zoom" This reverts commit b026e31d8d9ed64b1fe307f852df10292fffadf4. * Regenerate feature flag after merge * Apply feature flag to enable pan zoom wrappers * Add mini map toggle behind feature flag * Simplify minimap behavior * Update feature flag registry * Set minimap to false by default * fix gen-cue * Set toggles gen to main Add blank line to toggle gen csv * Add canvas pan zoom to csv * Remove old comment * Change ref parameter to be more descriptive * Rename visibleFun to be more descriptive * Consolidate transformScale transformRef in util * Remove non-null assertion on connection parentRect * Consolidate parentRect null coalescing into object * Remove minimap and change toggle * Add controls inline help for pan and zoom * Clean up mouse events * Pull scale out of ref and isolate transform * Remove transform ref from scene div * Fix context menu visible behavior * Fix connections and update util functions * Move transform component instance to util * fix backend test * minor updates * Clean up connections / fix minor bug where offset of arrow wasn't being calculated correctly * missed connection code cleanup * cleanup scene code a bit more * actually fix backend test * move eslint disable line closer to actual issue --------- Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
1 year ago
);
this.scene.updateSize(nextProps.width, nextProps.height);
this.scene.updateData(nextProps.data);
changed = true;
}
return changed;
}
openInlineEdit = () => {
if (isInlineEditOpen) {
this.forceUpdate();
this.setActivePanel();
return;
}
this.setActivePanel();
this.setState({ openInlineEdit: true });
isInlineEditOpen = true;
};
openSetBackground = (anchorPoint: AnchorPoint) => {
if (isSetBackgroundOpen) {
this.forceUpdate();
this.setActivePanel();
return;
}
this.setActivePanel();
this.setState({ openSetBackground: true });
this.setState({ contextMenuAnchorPoint: anchorPoint });
isSetBackgroundOpen = true;
};
tooltipCallback = (tooltip: CanvasTooltipPayload | undefined) => {
this.scene.tooltip = tooltip;
this.forceUpdate();
};
moveableActionCallback = (updated: boolean) => {
this.setState({ moveableAction: updated });
this.forceUpdate();
};
closeInlineEdit = () => {
this.setState({ openInlineEdit: false });
isInlineEditOpen = false;
};
closeSetBackground = () => {
this.setState({ openSetBackground: false });
isSetBackgroundOpen = false;
};
setActivePanel = () => {
activeCanvasPanel = this;
activePanelSubject.next({ panel: this });
};
renderInlineEdit = () => {
return <InlineEdit onClose={() => this.closeInlineEdit()} id={this.props.id} scene={this.scene} />;
};
renderSetBackground = () => {
return (
<SetBackground
onClose={() => this.closeSetBackground()}
scene={this.scene}
anchorPoint={this.state.contextMenuAnchorPoint}
/>
);
};
render() {
return (
<>
{this.scene.render()}
{this.state.openInlineEdit && this.renderInlineEdit()}
{this.state.openSetBackground && this.renderSetBackground()}
</>
);
}
}