mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Simplify repeating logic and panel menu interaction (#99352)
* Dashboard: Simplify repeating logic and panel menu interaction * Update * Remove unused behavior
This commit is contained in:
@@ -32,7 +32,13 @@ import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer';
|
||||
import { ShareModal } from '../sharing/ShareModal';
|
||||
import { DashboardInteractions } from '../utils/interactions';
|
||||
import { getEditPanelUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
|
||||
import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor, isLibraryPanel } from '../utils/utils';
|
||||
import {
|
||||
getDashboardSceneFor,
|
||||
getPanelIdForVizPanel,
|
||||
getQueryRunnerFor,
|
||||
isLibraryPanel,
|
||||
isReadOnlyClone,
|
||||
} from '../utils/utils';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { VizPanelLinks, VizPanelLinksMenu } from './PanelLinks';
|
||||
@@ -41,7 +47,7 @@ import { UnlinkLibraryPanelModal } from './UnlinkLibraryPanelModal';
|
||||
/**
|
||||
* Behavior is called when VizPanelMenu is activated (ie when it's opened).
|
||||
*/
|
||||
export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
export function panelMenuBehavior(menu: VizPanelMenu) {
|
||||
const asyncFunc = async () => {
|
||||
// hm.. add another generic param to SceneObject to specify parent type?
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
@@ -53,6 +59,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
const dashboard = getDashboardSceneFor(panel);
|
||||
const { isEmbedded } = dashboard.state.meta;
|
||||
const exploreMenuItem = await getExploreMenuItem(panel);
|
||||
const isReadOnlyRepeat = isReadOnlyClone(panel);
|
||||
|
||||
// For embedded dashboards we only have explore action for now
|
||||
if (isEmbedded) {
|
||||
@@ -72,7 +79,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
});
|
||||
}
|
||||
|
||||
if (dashboard.canEditDashboard() && dashboard.state.editable && !isRepeat && !isEditingPanel) {
|
||||
if (dashboard.canEditDashboard() && dashboard.state.editable && !isReadOnlyRepeat && !isEditingPanel) {
|
||||
// We could check isEditing here but I kind of think this should always be in the menu,
|
||||
// and going into panel edit should make the dashboard go into edit mode is it's not already
|
||||
items.push({
|
||||
@@ -167,7 +174,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
});
|
||||
}
|
||||
|
||||
if (dashboard.state.isEditing && !isRepeat && !isEditingPanel) {
|
||||
if (dashboard.state.isEditing && !isReadOnlyRepeat && !isEditingPanel) {
|
||||
moreSubMenu.push({
|
||||
text: t('panel.header-menu.duplicate', `Duplicate`),
|
||||
iconClassName: 'file-copy-alt',
|
||||
@@ -188,7 +195,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
});
|
||||
}
|
||||
|
||||
if (dashboard.state.isEditing && !isRepeat && !isEditingPanel) {
|
||||
if (dashboard.state.isEditing && !isReadOnlyRepeat && !isEditingPanel) {
|
||||
if (isLibraryPanel(panel)) {
|
||||
moreSubMenu.push({
|
||||
text: t('panel.header-menu.unlink-library-panel', `Unlink library panel`),
|
||||
@@ -263,7 +270,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
});
|
||||
}
|
||||
|
||||
if (dashboard.canEditDashboard() && plugin && !plugin.meta.skipDataQuery && !isRepeat) {
|
||||
if (dashboard.canEditDashboard() && plugin && !plugin.meta.skipDataQuery && !isReadOnlyRepeat) {
|
||||
moreSubMenu.push({
|
||||
text: t('panel.header-menu.get-help', 'Get help'),
|
||||
iconClassName: 'question-circle',
|
||||
@@ -313,7 +320,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
});
|
||||
}
|
||||
|
||||
if (dashboard.state.isEditing && !isRepeat && !isEditingPanel) {
|
||||
if (dashboard.state.isEditing && !isReadOnlyRepeat && !isEditingPanel) {
|
||||
items.push({
|
||||
text: '',
|
||||
type: 'divider',
|
||||
@@ -335,8 +342,6 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
asyncFunc();
|
||||
}
|
||||
|
||||
export const repeatPanelMenuBehavior = (menu: VizPanelMenu) => panelMenuBehavior(menu, true);
|
||||
|
||||
async function getExploreMenuItem(panel: VizPanel): Promise<PanelMenuItem | undefined> {
|
||||
const exploreUrl = await tryGetExploreUrlForPanel(panel);
|
||||
if (!exploreUrl) {
|
||||
|
||||
@@ -10,17 +10,15 @@ import {
|
||||
SceneVariableSet,
|
||||
TestVariable,
|
||||
VariableValueOption,
|
||||
VizPanel,
|
||||
VizPanelMenu,
|
||||
} from '@grafana/scenes';
|
||||
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants';
|
||||
|
||||
import { activateFullSceneTree } from '../utils/test-utils';
|
||||
import { isReadOnlyClone } from '../utils/utils';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { panelMenuBehavior, repeatPanelMenuBehavior } from './PanelMenuBehavior';
|
||||
import { RowRepeaterBehavior } from './RowRepeaterBehavior';
|
||||
import { DashboardGridItem, RepeatDirection } from './layout-default/DashboardGridItem';
|
||||
import { RepeatDirection } from './layout-default/DashboardGridItem';
|
||||
import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
|
||||
import { RowActions } from './row-actions/RowActions';
|
||||
|
||||
@@ -69,6 +67,13 @@ describe('RowRepeaterBehavior', () => {
|
||||
expect(gridItem.state.body?.state.key).toBe('canvas-1-clone-B1');
|
||||
});
|
||||
|
||||
it('Repeated rows should be read only', () => {
|
||||
const row1 = grid.state.children[1] as SceneGridRow;
|
||||
const row2 = grid.state.children[2] as SceneGridRow;
|
||||
expect(isReadOnlyClone(row1)).toBe(false);
|
||||
expect(isReadOnlyClone(row2)).toBe(true);
|
||||
});
|
||||
|
||||
it('Should update all rows when a panel is added to a clone', async () => {
|
||||
const originalRow = grid.state.children[1] as SceneGridRow;
|
||||
const clone1 = grid.state.children[2] as SceneGridRow;
|
||||
@@ -168,50 +173,6 @@ describe('RowRepeaterBehavior', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Given scene with DashboardGridItem', () => {
|
||||
let scene: DashboardScene;
|
||||
let grid: SceneGridLayout;
|
||||
let rowToRepeat: SceneGridRow;
|
||||
|
||||
beforeEach(async () => {
|
||||
const menu = new VizPanelMenu({
|
||||
$behaviors: [panelMenuBehavior],
|
||||
});
|
||||
|
||||
({ scene, grid, rowToRepeat } = buildScene({ variableQueryTime: 0 }));
|
||||
const panel = new VizPanel({ pluginId: 'text', menu });
|
||||
panel.getPlugin = () => getPanelPlugin({ skipDataQuery: false });
|
||||
|
||||
rowToRepeat.setState({
|
||||
children: [
|
||||
new DashboardGridItem({
|
||||
body: panel,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
activateFullSceneTree(scene);
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
});
|
||||
|
||||
it('Should set repeat specific panel menu for repeated rows but not original one', () => {
|
||||
const row1 = grid.state.children[1] as SceneGridRow;
|
||||
const row2 = grid.state.children[2] as SceneGridRow;
|
||||
const panelMenuBehaviorOriginal = (
|
||||
((row1.state.children[0] as DashboardGridItem).state.body as VizPanel).state.menu as VizPanelMenu
|
||||
).state.$behaviors;
|
||||
const panelMenuBehaviorClone = (
|
||||
((row2.state.children[0] as DashboardGridItem).state.body as VizPanel).state.menu as VizPanelMenu
|
||||
).state.$behaviors;
|
||||
|
||||
expect(panelMenuBehaviorOriginal).toBeDefined();
|
||||
expect(panelMenuBehaviorOriginal![0]).toBe(panelMenuBehavior);
|
||||
|
||||
expect(panelMenuBehaviorClone).toBeDefined();
|
||||
expect(panelMenuBehaviorClone![0]).toBe(repeatPanelMenuBehavior);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Given scene empty row', () => {
|
||||
let scene: DashboardScene;
|
||||
let grid: SceneGridLayout;
|
||||
|
||||
@@ -13,12 +13,10 @@ import {
|
||||
SceneVariableSet,
|
||||
VariableDependencyConfig,
|
||||
VariableValueSingle,
|
||||
VizPanelMenu,
|
||||
} from '@grafana/scenes';
|
||||
|
||||
import { getMultiVariableValues, getQueryRunnerFor } from '../utils/utils';
|
||||
|
||||
import { repeatPanelMenuBehavior } from './PanelMenuBehavior';
|
||||
import { DashboardGridItem } from './layout-default/DashboardGridItem';
|
||||
import { DashboardRepeatsProcessedEvent } from './types';
|
||||
|
||||
@@ -192,15 +190,7 @@ export class RowRepeaterBehavior extends SceneObjectBase<RowRepeaterBehaviorStat
|
||||
|
||||
//disallow clones to be dragged around or out of the row
|
||||
if (itemClone instanceof DashboardGridItem) {
|
||||
itemClone.setState({
|
||||
isDraggable: false,
|
||||
});
|
||||
|
||||
itemClone.state.body.setState({
|
||||
menu: new VizPanelMenu({
|
||||
$behaviors: [repeatPanelMenuBehavior],
|
||||
}),
|
||||
});
|
||||
itemClone.setState({ isDraggable: false });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SceneGridLayout, SceneVariableSet, TestVariable, VizPanel } from '@graf
|
||||
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants';
|
||||
|
||||
import { activateFullSceneTree, buildPanelRepeaterScene } from '../../utils/test-utils';
|
||||
import { isReadOnlyClone } from '../../utils/utils';
|
||||
import { DashboardScene } from '../DashboardScene';
|
||||
|
||||
import { DashboardGridItem, DashboardGridItemState } from './DashboardGridItem';
|
||||
@@ -41,6 +42,9 @@ describe('PanelRepeaterGridItem', () => {
|
||||
expect(panel1.state.$variables?.state.variables[0].getValue()).toBe('1');
|
||||
expect(panel1.state.$variables?.state.variables[0].getValueText?.()).toBe('A');
|
||||
expect(panel2.state.$variables?.state.variables[0].getValue()).toBe('2');
|
||||
|
||||
expect(isReadOnlyClone(panel1)).toBe(false);
|
||||
expect(isReadOnlyClone(panel2)).toBe(true);
|
||||
});
|
||||
|
||||
it('Should wait for variable to load', async () => {
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
MultiValueVariable,
|
||||
LocalValueVariable,
|
||||
CustomVariable,
|
||||
VizPanelMenu,
|
||||
VizPanelState,
|
||||
VariableValueSingle,
|
||||
SceneVariable,
|
||||
@@ -25,7 +24,6 @@ import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { getMultiVariableValues, getQueryRunnerFor } from '../../utils/utils';
|
||||
import { repeatPanelMenuBehavior } from '../PanelMenuBehavior';
|
||||
import { DashboardLayoutItem, DashboardRepeatsProcessedEvent } from '../types';
|
||||
|
||||
import { getDashboardGridItemOptions } from './DashboardGridItemEditor';
|
||||
@@ -142,11 +140,6 @@ export class DashboardGridItem
|
||||
}),
|
||||
key: `${panelToRepeat.state.key}-clone-${index}`,
|
||||
};
|
||||
if (index > 0) {
|
||||
cloneState.menu = new VizPanelMenu({
|
||||
$behaviors: [repeatPanelMenuBehavior],
|
||||
});
|
||||
}
|
||||
const clone = panelToRepeat.clone(cloneState);
|
||||
repeatedPanels.push(clone);
|
||||
}
|
||||
|
||||
@@ -216,6 +216,26 @@ export function isPanelClone(key: string) {
|
||||
return key.includes('clone');
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursivly check the scene graph up until it finds a read only clone.
|
||||
* If the key contains clone-0 it is the reference object and can be edited
|
||||
*/
|
||||
export function isReadOnlyClone(sceneObject: SceneObject): boolean {
|
||||
const key = sceneObject.state.key!;
|
||||
|
||||
// Regular expression to match 'clone-' followed by a number, but not 'clone-0' as the is the reference object
|
||||
const pattern = /clone-(?!0)/;
|
||||
if (pattern.test(key)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sceneObject.parent) {
|
||||
return isReadOnlyClone(sceneObject.parent);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getDefaultVizPanel(): VizPanel {
|
||||
return new VizPanel({
|
||||
title: 'Panel Title',
|
||||
|
||||
Reference in New Issue
Block a user