Dashboards: Refactor types for dynamic dashboards (#100064)

This commit is contained in:
Bogdan Matei 2025-02-05 11:08:41 +02:00 committed by GitHub
parent bea62aa615
commit f51571db5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 337 additions and 348 deletions

View File

@ -3435,10 +3435,6 @@ exports[`better eslint`] = {
"public/app/features/dashboard-scene/scene/UnlinkModal.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
],
"public/app/features/dashboard-scene/scene/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/features/dashboard-scene/serialization/angularMigration.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],

View File

@ -176,7 +176,7 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla
{openOverlay && (
<Resizable className={cx(styles.fixed, styles.container)} defaultSize={{ height: '100%', width: '20vw' }}>
<ElementEditPane element={editableElement} key={editableElement.getTypeName()} />
<ElementEditPane element={editableElement} key={editableElement.typeName} />
</Resizable>
)}
</>
@ -185,7 +185,7 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla
return (
<div className={styles.wrapper} ref={paneRef}>
<ElementEditPane element={editableElement} key={editableElement.getTypeName()} />
<ElementEditPane element={editableElement} key={editableElement.typeName} />
</div>
);
}

View File

@ -6,10 +6,11 @@ import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/Pan
import { DashboardScene } from '../scene/DashboardScene';
import { useLayoutCategory } from '../scene/layouts-shared/DashboardLayoutSelector';
import { EditableDashboardElement } from '../scene/types';
import { EditableDashboardElement } from '../scene/types/EditableDashboardElement';
export class DashboardEditableElement implements EditableDashboardElement {
public isEditableDashboardElement: true = true;
public readonly isEditableDashboardElement = true;
public readonly typeName = 'Dashboard';
public constructor(private dashboard: DashboardScene) {}
@ -47,10 +48,6 @@ export class DashboardEditableElement implements EditableDashboardElement {
return [dashboardOptions, layoutCategory];
}
public getTypeName(): string {
return 'Dashboard';
}
}
export function DashboardTitleInput({ dashboard }: { dashboard: DashboardScene }) {

View File

@ -4,7 +4,8 @@ import { GrafanaTheme2 } from '@grafana/data';
import { Stack, useStyles2 } from '@grafana/ui';
import { OptionsPaneCategory } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategory';
import { EditableDashboardElement, MultiSelectedEditableDashboardElement } from '../scene/types';
import { EditableDashboardElement } from '../scene/types/EditableDashboardElement';
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
export interface Props {
element: EditableDashboardElement | MultiSelectedEditableDashboardElement;
@ -19,7 +20,7 @@ export function ElementEditPane({ element }: Props) {
{element.renderActions && (
<OptionsPaneCategory
id="selected-item"
title={element.getTypeName()}
title={element.typeName}
isOpenDefault={true}
className={styles.noBorderTop}
>

View File

@ -2,12 +2,9 @@ import { SceneObject, SceneObjectRef, VizPanel } from '@grafana/scenes';
import { ElementSelectionContextItem } from '@grafana/ui';
import { DashboardScene } from '../scene/DashboardScene';
import {
EditableDashboardElement,
isBulkActionElement,
isEditableDashboardElement,
MultiSelectedEditableDashboardElement,
} from '../scene/types';
import { isBulkActionElement } from '../scene/types/BulkActionElement';
import { EditableDashboardElement, isEditableDashboardElement } from '../scene/types/EditableDashboardElement';
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
import { DashboardEditableElement } from './DashboardEditableElement';
import { MultiSelectedObjectsEditableElement } from './MultiSelectedObjectsEditableElement';

View File

@ -3,10 +3,13 @@ import { ReactNode } from 'react';
import { Stack, Text, Button } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { BulkActionElement, MultiSelectedEditableDashboardElement } from '../scene/types';
import { BulkActionElement } from '../scene/types/BulkActionElement';
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
export class MultiSelectedObjectsEditableElement implements MultiSelectedEditableDashboardElement {
public isMultiSelectedEditableDashboardElement: true = true;
public readonly isMultiSelectedEditableDashboardElement = true;
public readonly typeName = 'Objects';
private items?: BulkActionElement[];
constructor(items: BulkActionElement[]) {
@ -19,10 +22,6 @@ export class MultiSelectedObjectsEditableElement implements MultiSelectedEditabl
}
};
public getTypeName(): string {
return 'Objects';
}
renderActions(): ReactNode {
return (
<Stack direction="column">

View File

@ -5,11 +5,12 @@ import { Button, Stack, Text } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { MultiSelectedEditableDashboardElement } from '../scene/types';
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
export class MultiSelectedVizPanelsEditableElement implements MultiSelectedEditableDashboardElement {
public isMultiSelectedEditableDashboardElement: true = true;
public readonly isMultiSelectedEditableDashboardElement = true;
public readonly typeName = 'Panels';
private items?: VizPanel[];
@ -34,10 +35,6 @@ export class MultiSelectedVizPanelsEditableElement implements MultiSelectedEdita
}
};
public getTypeName(): string {
return 'Panels';
}
renderActions(): ReactNode {
return (
<Stack direction="column">

View File

@ -12,11 +12,14 @@ import {
PanelDescriptionTextArea,
PanelFrameTitleInput,
} from '../panel-edit/getPanelFrameOptions';
import { BulkActionElement, EditableDashboardElement, isDashboardLayoutItem } from '../scene/types';
import { BulkActionElement } from '../scene/types/BulkActionElement';
import { isDashboardLayoutItem } from '../scene/types/DashboardLayoutItem';
import { EditableDashboardElement } from '../scene/types/EditableDashboardElement';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
export class VizPanelEditableElement implements EditableDashboardElement, BulkActionElement {
public isEditableDashboardElement: true = true;
public readonly isEditableDashboardElement = true;
public readonly typeName = 'Panel';
public constructor(private panel: VizPanel) {}
@ -96,10 +99,6 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
return categories;
}
public getTypeName(): string {
return 'Panel';
}
public onDelete = () => {
const layout = dashboardSceneGraph.getLayoutManagerFor(this.panel);
layout.removePanel(this.panel);

View File

@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { EditableDashboardElement, MultiSelectedEditableDashboardElement } from '../scene/types';
import { EditableDashboardElement } from '../scene/types/EditableDashboardElement';
import { MultiSelectedEditableDashboardElement } from '../scene/types/MultiSelectedEditableDashboardElement';
import { ElementSelection } from './ElementSelection';

View File

@ -21,7 +21,7 @@ import { saveLibPanel } from 'app/features/library-panels/state/api';
import { DashboardSceneChangeTracker } from '../saving/DashboardSceneChangeTracker';
import { getPanelChanges } from '../saving/getDashboardChanges';
import { DashboardLayoutItem, isDashboardLayoutItem } from '../scene/types';
import { DashboardLayoutItem, isDashboardLayoutItem } from '../scene/types/DashboardLayoutItem';
import { vizPanelToPanel } from '../serialization/transformSceneToSaveModel';
import {
activateSceneObjectAndParentTree,

View File

@ -10,7 +10,7 @@ import { getPanelLinksVariableSuggestions } from 'app/features/panel/panellinks/
import { VizPanelLinks } from '../scene/PanelLinks';
import { PanelTimeRange } from '../scene/PanelTimeRange';
import { isDashboardLayoutItem } from '../scene/types';
import { isDashboardLayoutItem } from '../scene/types/DashboardLayoutItem';
import { vizPanelToPanel, transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getDashboardSceneFor } from '../utils/utils';

View File

@ -71,7 +71,7 @@ import { isUsingAngularDatasourcePlugin, isUsingAngularPanelPlugin } from './ang
import { setupKeyboardShortcuts } from './keyboardShortcuts';
import { DashboardGridItem } from './layout-default/DashboardGridItem';
import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
import { DashboardLayoutManager } from './types';
import { DashboardLayoutManager } from './types/DashboardLayoutManager';
export const PERSISTED_PROPS = ['title', 'description', 'tags', 'editable', 'graphTooltip', 'links', 'meta', 'preload'];
export const PANEL_SEARCH_VAR = 'systemPanelFilterVar';
@ -258,7 +258,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
this.setState({ isEditing: true, showHiddenElements: true });
// Propagate change edit mode change to children
this.state.body.editModeChanged(true);
this.state.body.editModeChanged?.(true);
// Propagate edit mode to scopes
this._scopesFacade?.enterReadOnly();
@ -349,7 +349,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
}
// Disable grid dragging
this.state.body.editModeChanged(false);
this.state.body.editModeChanged?.(false);
}
public canDiscard() {

View File

@ -8,7 +8,7 @@ import { getCloneKey } from '../utils/clone';
import { DashboardScene } from './DashboardScene';
import { DashboardGridItem } from './layout-default/DashboardGridItem';
import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
import { DashboardRepeatsProcessedEvent } from './types';
import { DashboardRepeatsProcessedEvent } from './types/DashboardRepeatsProcessedEvent';
describe('DashboardSceneUrlSync', () => {
describe('Given a standard scene', () => {

View File

@ -19,7 +19,7 @@ import { DashboardScene, DashboardSceneState } from './DashboardScene';
import { LibraryPanelBehavior } from './LibraryPanelBehavior';
import { ViewPanelScene } from './ViewPanelScene';
import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
import { DashboardRepeatsProcessedEvent } from './types';
import { DashboardRepeatsProcessedEvent } from './types/DashboardRepeatsProcessedEvent';
export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
private _eventSub?: Unsubscribable;

View File

@ -25,7 +25,8 @@ import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components
import { getCloneKey } from '../../utils/clone';
import { getMultiVariableValues, getQueryRunnerFor } from '../../utils/utils';
import { DashboardLayoutItem, DashboardRepeatsProcessedEvent } from '../types';
import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
import { getDashboardGridItemOptions } from './DashboardGridItemEditor';
@ -47,6 +48,8 @@ export class DashboardGridItem
private _prevRepeatValues?: VariableValueSingle[];
protected _variableDependency = new DashboardGridItemVariableDependencyHandler(this);
public readonly isDashboardLayoutItem = true;
public constructor(state: DashboardGridItemState) {
super(state);
@ -188,11 +191,6 @@ export class DashboardGridItem
this.setState(stateUpdate);
}
/**
* DashboardLayoutItem interface start
*/
public isDashboardLayoutItem: true = true;
/**
* Returns options for panel edit
*/

View File

@ -26,19 +26,19 @@ describe('DefaultGridLayoutManager', () => {
});
});
describe('getNextPanelId', () => {
it('should get next panel id in a simple 3 panel layout', () => {
describe('getMaxPanelId', () => {
it('should get max panel id in a simple 3 panel layout', () => {
const { manager } = setup();
const id = manager.getNextPanelId();
const id = manager.getMaxPanelId();
expect(id).toBe(4);
expect(id).toBe(3);
});
it('should return 1 if no panels are found', () => {
it('should return 0 if no panels are found', () => {
const { manager } = setup({ gridItems: [] });
const id = manager.getNextPanelId();
const id = manager.getMaxPanelId();
expect(id).toBe(1);
expect(id).toBe(0);
});
});

View File

@ -22,7 +22,7 @@ import {
getGridItemKeyForPanelId,
getDashboardSceneFor,
} from '../../utils/utils';
import { DashboardLayoutManager, LayoutRegistryItem } from '../types';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { DashboardGridItem } from './DashboardGridItem';
import { RowRepeaterBehavior } from './RowRepeaterBehavior';
@ -39,7 +39,16 @@ export class DefaultGridLayoutManager
extends SceneObjectBase<DefaultGridLayoutManagerState>
implements DashboardLayoutManager
{
public isDashboardLayoutManager: true = true;
public readonly isDashboardLayoutManager = true;
public static readonly descriptor = {
name: 'Default grid',
description: 'The default grid layout',
id: 'default-grid',
createFromLayout: DefaultGridLayoutManager.createFromLayout,
};
public readonly descriptor = DefaultGridLayoutManager.descriptor;
public editModeChanged(isEditing: boolean): void {
const updateResizeAndDragging = () => {
@ -328,10 +337,6 @@ export class DefaultGridLayoutManager
});
}
public getDescriptor(): LayoutRegistryItem {
return DefaultGridLayoutManager.getDescriptor();
}
public cloneLayout(ancestorKey: string, isSource: boolean): DashboardLayoutManager {
return this.clone({
grid: this.state.grid.clone({
@ -402,15 +407,6 @@ export class DefaultGridLayoutManager
});
}
public static getDescriptor(): LayoutRegistryItem {
return {
name: 'Default grid',
description: 'The default grid layout',
id: 'default-grid',
createFromLayout: DefaultGridLayoutManager.createFromLayout,
};
}
/**
* Handle switching to the manual grid layout from other layouts
* @param currentLayout

View File

@ -23,7 +23,7 @@ import {
isClonedKey,
} from '../../utils/clone';
import { getMultiVariableValues } from '../../utils/utils';
import { DashboardRepeatsProcessedEvent } from '../types';
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
import { DashboardGridItem } from './DashboardGridItem';

View File

@ -6,7 +6,7 @@ import { Switch, useStyles2 } from '@grafana/ui';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { DashboardLayoutItem } from '../types';
import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
export interface ResponsiveGridItemState extends SceneObjectState {
body: VizPanel;
@ -14,6 +14,8 @@ export interface ResponsiveGridItemState extends SceneObjectState {
}
export class ResponsiveGridItem extends SceneObjectBase<ResponsiveGridItemState> implements DashboardLayoutItem {
public readonly isDashboardLayoutItem = true;
public constructor(state: ResponsiveGridItemState) {
super(state);
this.addActivationHandler(() => this._activationHandler());
@ -29,11 +31,6 @@ export class ResponsiveGridItem extends SceneObjectBase<ResponsiveGridItemState>
this.setState({ hideWhenNoData: !this.state.hideWhenNoData });
}
/**
* DashboardLayoutElement interface
*/
public isDashboardLayoutItem: true = true;
public getOptions?(): OptionsPaneCategoryDescriptor {
const model = this;

View File

@ -5,7 +5,7 @@ import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/Pan
import { getDashboardSceneFor, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../../utils/utils';
import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager';
import { DashboardLayoutManager, LayoutRegistryItem } from '../types';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { ResponsiveGridItem } from './ResponsiveGridItem';
@ -17,7 +17,16 @@ export class ResponsiveGridLayoutManager
extends SceneObjectBase<ResponsiveGridLayoutManagerState>
implements DashboardLayoutManager
{
public isDashboardLayoutManager: true = true;
public readonly isDashboardLayoutManager = true;
public static readonly descriptor = {
name: 'Responsive grid',
description: 'CSS layout that adjusts to the available space',
id: 'responsive-grid',
createFromLayout: ResponsiveGridLayoutManager.createFromLayout,
};
public readonly descriptor = ResponsiveGridLayoutManager.descriptor;
public editModeChanged(isEditing: boolean): void {}
@ -83,19 +92,6 @@ export class ResponsiveGridLayoutManager
return getOptions(this);
}
public getDescriptor(): LayoutRegistryItem {
return ResponsiveGridLayoutManager.getDescriptor();
}
public static getDescriptor(): LayoutRegistryItem {
return {
name: 'Responsive grid',
description: 'CSS layout that adjusts to the available space',
id: 'responsive-grid',
createFromLayout: ResponsiveGridLayoutManager.createFromLayout,
};
}
public static createEmpty() {
return new ResponsiveGridLayoutManager({
layout: new SceneCSSGridLayout({
@ -123,10 +119,6 @@ export class ResponsiveGridLayoutManager
});
}
toSaveModel?() {
throw new Error('Method not implemented.');
}
activateRepeaters?(): void {
throw new Error('Method not implemented.');
}

View File

@ -6,12 +6,13 @@ import { t, Trans } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { MultiSelectedEditableDashboardElement } from '../types';
import { MultiSelectedEditableDashboardElement } from '../types/MultiSelectedEditableDashboardElement';
import { RowItem } from './RowItem';
export class MultiSelectedRowItemsElement implements MultiSelectedEditableDashboardElement {
public isMultiSelectedEditableDashboardElement: true = true;
public readonly isMultiSelectedEditableDashboardElement = true;
public readonly typeName = 'Rows';
private items?: RowItem[];
@ -44,10 +45,6 @@ export class MultiSelectedRowItemsElement implements MultiSelectedEditableDashbo
return [rowOptions];
}
public getTypeName(): string {
return 'Rows';
}
public onDelete = () => {
for (const item of this.items || []) {
item.onDelete();

View File

@ -33,7 +33,10 @@ import { isClonedKey } from '../../utils/clone';
import { getDashboardSceneFor, getDefaultVizPanel, getQueryRunnerFor } from '../../utils/utils';
import { DashboardScene } from '../DashboardScene';
import { useLayoutCategory } from '../layouts-shared/DashboardLayoutSelector';
import { BulkActionElement, DashboardLayoutManager, EditableDashboardElement, LayoutParent } from '../types';
import { BulkActionElement } from '../types/BulkActionElement';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { EditableDashboardElement } from '../types/EditableDashboardElement';
import { LayoutParent } from '../types/LayoutParent';
import { MultiSelectedRowItemsElement } from './MultiSelectedRowItemsElement';
import { RowItemRepeaterBehavior } from './RowItemRepeaterBehavior';
@ -55,7 +58,8 @@ export class RowItem
statePaths: ['title'],
});
public isEditableDashboardElement: true = true;
public readonly isEditableDashboardElement = true;
public readonly typeName = 'Row';
public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] {
const row = this;
@ -107,10 +111,6 @@ export class RowItem
return [rowOptions, rowRepeatOptions, layoutOptions];
}
public getTypeName(): string {
return 'Row';
}
public createMultiSelectedElement(items: SceneObject[]) {
return new MultiSelectedRowItemsElement(items);
}

View File

@ -13,7 +13,7 @@ import {
import { isClonedKeyOf, getCloneKey } from '../../utils/clone';
import { getMultiVariableValues } from '../../utils/utils';
import { DashboardRepeatsProcessedEvent } from '../types';
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
import { RowItem } from './RowItem';
import { RowsLayoutManager } from './RowsLayoutManager';

View File

@ -18,7 +18,7 @@ import { DashboardGridItem } from '../layout-default/DashboardGridItem';
import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager';
import { RowRepeaterBehavior } from '../layout-default/RowRepeaterBehavior';
import { ResponsiveGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager';
import { DashboardLayoutManager, LayoutRegistryItem } from '../types';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { RowItem } from './RowItem';
import { RowItemRepeaterBehavior } from './RowItemRepeaterBehavior';
@ -28,7 +28,16 @@ interface RowsLayoutManagerState extends SceneObjectState {
}
export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> implements DashboardLayoutManager {
public isDashboardLayoutManager: true = true;
public readonly isDashboardLayoutManager = true;
public static readonly descriptor = {
name: 'Rows',
description: 'Rows layout',
id: 'rows-layout',
createFromLayout: RowsLayoutManager.createFromLayout,
};
public readonly descriptor = RowsLayoutManager.descriptor;
public editModeChanged(isEditing: boolean): void {}
@ -113,23 +122,10 @@ export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> i
});
}
public getDescriptor(): LayoutRegistryItem {
return RowsLayoutManager.getDescriptor();
}
public getSelectedObject() {
return sceneGraph.getAncestor(this, DashboardScene).state.editPane.state.selection?.getFirstObject();
}
public static getDescriptor(): LayoutRegistryItem {
return {
name: 'Rows',
description: 'Rows layout',
id: 'rows-layout',
createFromLayout: RowsLayoutManager.createFromLayout,
};
}
public static createEmpty() {
return new RowsLayoutManager({ rows: [] });
}

View File

@ -4,7 +4,9 @@ import { Select } from '@grafana/ui';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { DashboardLayoutManager, isLayoutParent, LayoutRegistryItem } from '../types';
import { DashboardLayoutManager } from '../types/DashboardLayoutManager';
import { isLayoutParent } from '../types/LayoutParent';
import { LayoutRegistryItem } from '../types/LayoutRegistryItem';
import { layoutRegistry } from './layoutRegistry';
import { findParentLayout } from './utils';
@ -16,7 +18,7 @@ export interface Props {
export function DashboardLayoutSelector({ layoutManager }: Props) {
const options = useMemo(() => {
const parentLayout = findParentLayout(layoutManager);
const parentLayoutId = parentLayout?.getDescriptor().id;
const parentLayoutId = parentLayout?.descriptor.id;
return layoutRegistry
.list()
@ -27,7 +29,7 @@ export function DashboardLayoutSelector({ layoutManager }: Props) {
}));
}, [layoutManager]);
const currentLayoutId = layoutManager.getDescriptor().id;
const currentLayoutId = layoutManager.descriptor.id;
const currentOption = options.find((option) => option.value.id === currentLayoutId);
return (

View File

@ -3,12 +3,8 @@ import { Registry } from '@grafana/data';
import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager';
import { ResponsiveGridLayoutManager } from '../layout-responsive-grid/ResponsiveGridLayoutManager';
import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager';
import { LayoutRegistryItem } from '../types';
import { LayoutRegistryItem } from '../types/LayoutRegistryItem';
export const layoutRegistry: Registry<LayoutRegistryItem> = new Registry<LayoutRegistryItem>(() => {
return [
DefaultGridLayoutManager.getDescriptor(),
ResponsiveGridLayoutManager.getDescriptor(),
RowsLayoutManager.getDescriptor(),
];
return [DefaultGridLayoutManager.descriptor, ResponsiveGridLayoutManager.descriptor, RowsLayoutManager.descriptor];
});

View File

@ -1,6 +1,6 @@
import { SceneObject } from '@grafana/scenes';
import { DashboardLayoutManager, isDashboardLayoutManager } from '../types';
import { DashboardLayoutManager, isDashboardLayoutManager } from '../types/DashboardLayoutManager';
export function findParentLayout(sceneObject: SceneObject): DashboardLayoutManager | null {
let parent = sceneObject.parent;

View File

@ -1,207 +0,0 @@
import { BusEventWithPayload, RegistryItem } from '@grafana/data';
import { SceneObject, VizPanel } from '@grafana/scenes';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
/**
* A scene object that usually wraps an underlying layout
* Dealing with all the state management and editing of the layout
*/
export interface DashboardLayoutManager extends SceneObject {
/** Marks it as a DashboardLayoutManager */
isDashboardLayoutManager: true;
/**
* Notify the layout manager that the edit mode has changed
* @param isEditing
*/
editModeChanged(isEditing: boolean): void;
/**
* Remove an element / panel
* @param element
*/
removePanel(panel: VizPanel): void;
/**
* Creates a copy of an existing element and adds it to the layout
* @param element
*/
duplicatePanel(panel: VizPanel): void;
/**
* Adds a new panel to the layout
*/
addPanel(panel: VizPanel): void;
/**
* Add row
*/
addNewRow(): void;
/**
* getVizPanels
*/
getVizPanels(): VizPanel[];
/**
* Turn into a save model
* @param saveModel
*/
toSaveModel?(): any;
/**
* For dynamic panels that need to be viewed in isolation (SoloRoute)
*/
activateRepeaters?(): void;
/**
* Gets the layout descriptor (which has the name and id)
*/
getDescriptor(): LayoutRegistryItem;
/**
* Renders options and layout actions
*/
getOptions?(): OptionsPaneItemDescriptor[];
/**
* Create a clone of the layout manager given an ancestor key
* @param ancestorKey
* @param isSource
*/
cloneLayout?(ancestorKey: string, isSource: boolean): DashboardLayoutManager;
/**
* Returns the highest panel id in the layout
*/
getMaxPanelId(): number;
}
export function isDashboardLayoutManager(obj: SceneObject): obj is DashboardLayoutManager {
return 'isDashboardLayoutManager' in obj;
}
/**
* The layout descriptor used when selecting / switching layouts
*/
export interface LayoutRegistryItem extends RegistryItem {
/**
* When switching between layouts
* @param currentLayout
*/
createFromLayout(currentLayout: DashboardLayoutManager): DashboardLayoutManager;
/**
* Create from persisted state
* @param saveModel
*/
createFromSaveModel?(saveModel: any): void;
}
/**
* This interface is needed to support layouts existing on different levels of the scene (DashboardScene and inside the TabsLayoutManager)
*/
export interface LayoutParent extends SceneObject {
switchLayout(newLayout: DashboardLayoutManager): void;
}
export function isLayoutParent(obj: SceneObject): obj is LayoutParent {
return 'switchLayout' in obj;
}
/**
* Abstraction to handle editing of different layout elements (wrappers for VizPanels and other objects)
* Also useful to when rendering / viewing an element outside it's layout scope
*/
export interface DashboardLayoutItem extends SceneObject {
/**
* Marks this object as a layout item
*/
isDashboardLayoutItem: true;
/**
* Return layout item options (like repeat, repeat direction, etc for the default DashboardGridItem)
*/
getOptions?(): OptionsPaneCategoryDescriptor;
/**
* When going into panel edit
**/
editingStarted?(): void;
/**
* When coming out of panel edit
*/
editingCompleted?(withChanges: boolean): void;
}
export function isDashboardLayoutItem(obj: SceneObject): obj is DashboardLayoutItem {
return 'isDashboardLayoutItem' in obj;
}
export interface DashboardRepeatsProcessedEventPayload {
source: SceneObject;
}
export class DashboardRepeatsProcessedEvent extends BusEventWithPayload<DashboardRepeatsProcessedEventPayload> {
public static type = 'dashboard-repeats-processed';
}
/**
* Interface for elements that have options
*/
export interface EditableDashboardElement {
/**
* Marks this object as an element that can be selected and edited directly on the canvas
*/
isEditableDashboardElement: true;
/**
* Hook that returns edit pane options
*/
useEditPaneOptions(): OptionsPaneCategoryDescriptor[];
/**
* Get the type name of the element
*/
getTypeName(): string;
/**
* Panel Actions
**/
renderActions?(): React.ReactNode;
/**
* creates a new multi-selection element from a list of selected items
*/
createMultiSelectedElement?(items: SceneObject[]): MultiSelectedEditableDashboardElement;
}
export function isEditableDashboardElement(obj: object): obj is EditableDashboardElement {
return 'isEditableDashboardElement' in obj;
}
export interface MultiSelectedEditableDashboardElement {
/**
* Marks this object as an element that can be selected and edited directly on the canvas
*/
isMultiSelectedEditableDashboardElement: true;
/**
* Get the type name of the element
*/
getTypeName(): string;
/**
* Hook that returns edit pane options
*/
useEditPaneOptions?(): OptionsPaneCategoryDescriptor[];
/**
* Panel Actions
**/
renderActions?(): React.ReactNode;
}
export function isMultiSelectedEditableDashboardElement(obj: object): obj is MultiSelectedEditableDashboardElement {
return 'isMultiSelectedEditableDashboardElement' in obj;
}
export interface BulkActionElement {
onDelete(): void;
onCopy?(): void;
}
export function isBulkActionElement(obj: object): obj is BulkActionElement {
return 'onDelete' in obj;
}

View File

@ -0,0 +1,8 @@
export interface BulkActionElement {
onDelete(): void;
onCopy?(): void;
}
export function isBulkActionElement(obj: object): obj is BulkActionElement {
return 'onDelete' in obj;
}

View File

@ -0,0 +1,32 @@
import { SceneObject } from '@grafana/scenes';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
/**
* Abstraction to handle editing of different layout elements (wrappers for VizPanels and other objects)
* Also useful to when rendering / viewing an element outside it's layout scope
*/
export interface DashboardLayoutItem extends SceneObject {
/**
* Marks this object as a layout item
*/
isDashboardLayoutItem: true;
/**
* Return layout item options (like repeat, repeat direction, etc. for the default DashboardGridItem)
*/
getOptions?(): OptionsPaneCategoryDescriptor;
/**
* When going into panel edit
**/
editingStarted?(): void;
/**
* When coming out of panel edit
*/
editingCompleted?(withChanges: boolean): void;
}
export function isDashboardLayoutItem(obj: SceneObject): obj is DashboardLayoutItem {
return 'isDashboardLayoutItem' in obj;
}

View File

@ -0,0 +1,82 @@
import { SceneObject, VizPanel } from '@grafana/scenes';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { LayoutRegistryItem } from './LayoutRegistryItem';
/**
* A scene object that usually wraps an underlying layout
* Dealing with all the state management and editing of the layout
*/
export interface DashboardLayoutManager<S = {}> extends SceneObject {
/** Marks it as a DashboardLayoutManager */
isDashboardLayoutManager: true;
/**
* The layout descriptor (which has the name and id)
*/
descriptor: Readonly<LayoutRegistryItem>;
/**
* Adds a new panel to the layout
*/
addPanel(panel: VizPanel): void;
/**
* Remove an element / panel
* @param panel
*/
removePanel(panel: VizPanel): void;
/**
* Creates a copy of an existing element and adds it to the layout
* @param panel
*/
duplicatePanel(panel: VizPanel): void;
/**
* getVizPanels
*/
getVizPanels(): VizPanel[];
/**
* Returns the highest panel id in the layout
*/
getMaxPanelId(): number;
/**
* Add row
*/
addNewRow(): void;
/**
* Notify the layout manager that the edit mode has changed
* @param isEditing
*/
editModeChanged?(isEditing: boolean): void;
/**
* Turn into a save model
*/
toSaveModel?(): S;
/**
* For dynamic panels that need to be viewed in isolation (SoloRoute)
*/
activateRepeaters?(): void;
/**
* Renders options and layout actions
*/
getOptions?(): OptionsPaneItemDescriptor[];
/**
* Create a clone of the layout manager given an ancestor key
* @param ancestorKey
* @param isSource
*/
cloneLayout?(ancestorKey: string, isSource: boolean): DashboardLayoutManager;
}
export function isDashboardLayoutManager(obj: SceneObject): obj is DashboardLayoutManager {
return 'isDashboardLayoutManager' in obj;
}

View File

@ -0,0 +1,10 @@
import { BusEventWithPayload } from '@grafana/data';
import { SceneObject } from '@grafana/scenes';
export interface DashboardRepeatsProcessedEventPayload {
source: SceneObject;
}
export class DashboardRepeatsProcessedEvent extends BusEventWithPayload<DashboardRepeatsProcessedEventPayload> {
public static type = 'dashboard-repeats-processed';
}

View File

@ -0,0 +1,40 @@
import { ReactNode } from 'react';
import { SceneObject } from '@grafana/scenes';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { MultiSelectedEditableDashboardElement } from './MultiSelectedEditableDashboardElement';
/**
* Interface for elements that have options
*/
export interface EditableDashboardElement {
/**
* Marks this object as an element that can be selected and edited directly on the canvas
*/
isEditableDashboardElement: true;
/**
* Type name of the element
*/
typeName: Readonly<string>;
/**
* Hook that returns edit pane options
*/
useEditPaneOptions(): OptionsPaneCategoryDescriptor[];
/**
* Panel Actions
**/
renderActions?(): ReactNode;
/**
* creates a new multi-selection element from a list of selected items
*/
createMultiSelectedElement?(items: SceneObject[]): MultiSelectedEditableDashboardElement;
}
export function isEditableDashboardElement(obj: object): obj is EditableDashboardElement {
return 'isEditableDashboardElement' in obj;
}

View File

@ -0,0 +1,14 @@
import { SceneObject } from '@grafana/scenes';
import { DashboardLayoutManager } from './DashboardLayoutManager';
/**
* This interface is needed to support layouts existing on different levels of the scene (DashboardScene and inside the TabsLayoutManager)
*/
export interface LayoutParent extends SceneObject {
switchLayout(newLayout: DashboardLayoutManager): void;
}
export function isLayoutParent(obj: SceneObject): obj is LayoutParent {
return 'switchLayout' in obj;
}

View File

@ -0,0 +1,20 @@
import { RegistryItem } from '@grafana/data';
import { DashboardLayoutManager } from './DashboardLayoutManager';
/**
* The layout descriptor used when selecting / switching layouts
*/
export interface LayoutRegistryItem<S = {}> extends RegistryItem {
/**
* When switching between layouts
* @param currentLayout
*/
createFromLayout(currentLayout: DashboardLayoutManager): DashboardLayoutManager;
/**
* Create from persisted state
* @param saveModel
*/
createFromSaveModel?(saveModel: S): void;
}

View File

@ -0,0 +1,29 @@
import { ReactNode } from 'react';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
export interface MultiSelectedEditableDashboardElement {
/**
* Marks this object as an element that can be selected and edited directly on the canvas
*/
isMultiSelectedEditableDashboardElement: true;
/**
* Type name of the element
*/
typeName: Readonly<string>;
/**
* Hook that returns edit pane options
*/
useEditPaneOptions?(): OptionsPaneCategoryDescriptor[];
/**
* Panel Actions
**/
renderActions?(): ReactNode;
}
export function isMultiSelectedEditableDashboardElement(obj: object): obj is MultiSelectedEditableDashboardElement {
return 'isMultiSelectedEditableDashboardElement' in obj;
}

View File

@ -33,7 +33,7 @@ import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSou
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { DashboardLayoutManager } from '../scene/types';
import { DashboardLayoutManager } from '../scene/types/DashboardLayoutManager';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getQueryRunnerFor } from '../utils/utils';
import { validateVariable, validateVizPanel } from '../v2schema/test-helpers';

View File

@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
import { VizPanel, UrlSyncManager } from '@grafana/scenes';
import { DashboardScene } from '../scene/DashboardScene';
import { DashboardRepeatsProcessedEvent } from '../scene/types';
import { DashboardRepeatsProcessedEvent } from '../scene/types/DashboardRepeatsProcessedEvent';
import { containsCloneKey } from '../utils/clone';
import { findVizPanelByKey } from '../utils/utils';

View File

@ -19,7 +19,7 @@ import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
import { panelMenuBehavior } from '../scene/PanelMenuBehavior';
import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem';
import { DashboardLayoutManager, isDashboardLayoutManager } from '../scene/types';
import { DashboardLayoutManager, isDashboardLayoutManager } from '../scene/types/DashboardLayoutManager';
import { getLastKeyFromClone, getOriginalKey } from './clone';