Scenes: Row controls (#83607)
* wip row controls * wip row repeats * wip * wip * row repeat functional * refactor * refactor to reuse RepeatRowSelect2 * refactor + tests * remove comment * refactor
This commit is contained in:
@@ -15,12 +15,15 @@ interface Props {
|
||||
}
|
||||
|
||||
export const PanelOptions = React.memo<Props>(({ vizManager, searchQuery, listMode }) => {
|
||||
const { panel } = vizManager.useState();
|
||||
const { panel, repeat } = vizManager.useState();
|
||||
const { data } = sceneGraph.getData(panel).useState();
|
||||
const { options, fieldConfig } = panel.useState();
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const panelFrameOptions = useMemo(() => getPanelFrameCategory2(vizManager), [vizManager, panel]);
|
||||
const panelFrameOptions = useMemo(
|
||||
() => getPanelFrameCategory2(vizManager, panel, repeat),
|
||||
[vizManager, panel, repeat]
|
||||
);
|
||||
|
||||
const visualizationOptions = useMemo(() => {
|
||||
const plugin = panel.getPlugin();
|
||||
|
||||
@@ -338,6 +338,47 @@ describe('DashboardScene', () => {
|
||||
expect(gridRow.state.children.length).toBe(0);
|
||||
});
|
||||
|
||||
it('Should remove a row and move its children to the grid layout', () => {
|
||||
const body = scene.state.body as SceneGridLayout;
|
||||
const row = body.state.children[2] as SceneGridRow;
|
||||
|
||||
scene.removeRow(row);
|
||||
|
||||
const vizPanel = (body.state.children[2] as SceneGridItem).state.body as VizPanel;
|
||||
|
||||
expect(body.state.children.length).toBe(6);
|
||||
expect(vizPanel.state.key).toBe('panel-4');
|
||||
});
|
||||
|
||||
it('Should remove a row and its children', () => {
|
||||
const body = scene.state.body as SceneGridLayout;
|
||||
const row = body.state.children[2] as SceneGridRow;
|
||||
|
||||
scene.removeRow(row, true);
|
||||
|
||||
expect(body.state.children.length).toBe(4);
|
||||
});
|
||||
|
||||
it('Should remove an empty row from the layout', () => {
|
||||
const row = new SceneGridRow({
|
||||
key: 'panel-1',
|
||||
});
|
||||
|
||||
const scene = buildTestScene({
|
||||
body: new SceneGridLayout({
|
||||
children: [row],
|
||||
}),
|
||||
});
|
||||
|
||||
const body = scene.state.body as SceneGridLayout;
|
||||
|
||||
expect(body.state.children.length).toBe(1);
|
||||
|
||||
scene.removeRow(row);
|
||||
|
||||
expect(body.state.children.length).toBe(0);
|
||||
});
|
||||
|
||||
it('Should fail to copy a panel if it does not have a grid item parent', () => {
|
||||
const vizPanel = new VizPanel({
|
||||
title: 'Panel Title',
|
||||
|
||||
@@ -412,6 +412,25 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
});
|
||||
}
|
||||
|
||||
public removeRow(row: SceneGridRow, removePanels = false) {
|
||||
if (!(this.state.body instanceof SceneGridLayout)) {
|
||||
throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
|
||||
}
|
||||
|
||||
const sceneGridLayout = this.state.body;
|
||||
|
||||
const children = sceneGridLayout.state.children.filter((child) => child.state.key !== row.state.key);
|
||||
|
||||
if (!removePanels) {
|
||||
const rowChildren = row.state.children.map((child) => child.clone());
|
||||
const indexOfRow = sceneGridLayout.state.children.findIndex((child) => child.state.key === row.state.key);
|
||||
|
||||
children.splice(indexOfRow, 0, ...rowChildren);
|
||||
}
|
||||
|
||||
sceneGridLayout.setState({ children });
|
||||
}
|
||||
|
||||
public addPanel(vizPanel: VizPanel): void {
|
||||
if (!(this.state.body instanceof SceneGridLayout)) {
|
||||
throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
|
||||
|
||||
@@ -50,6 +50,19 @@ describe('RowRepeaterBehavior', () => {
|
||||
expect(rowAtTheBottom.state.y).toBe(40);
|
||||
});
|
||||
|
||||
it('Should push row at the bottom down and also offset its children', () => {
|
||||
const rowAtTheBottom = grid.state.children[6] as SceneGridRow;
|
||||
const rowChildOne = rowAtTheBottom.state.children[0] as SceneGridItem;
|
||||
const rowChildTwo = rowAtTheBottom.state.children[1] as SceneGridItem;
|
||||
|
||||
expect(rowAtTheBottom.state.title).toBe('Row at the bottom');
|
||||
|
||||
// Panel at the top is 10, each row is (1+5)*5 = 30, so the grid item below it should be 40
|
||||
expect(rowAtTheBottom.state.y).toBe(40);
|
||||
expect(rowChildOne.state.y).toBe(41);
|
||||
expect(rowChildTwo.state.y).toBe(49);
|
||||
});
|
||||
|
||||
it('Should handle second repeat cycle and update remove old repeats', async () => {
|
||||
// trigger another repeat cycle by changing the variable
|
||||
const variable = scene.state.$variables!.state.variables[0] as TestVariable;
|
||||
@@ -111,6 +124,26 @@ function buildScene(options: SceneOptions) {
|
||||
width: 24,
|
||||
height: 5,
|
||||
title: 'Row at the bottom',
|
||||
children: [
|
||||
new SceneGridItem({
|
||||
key: 'griditem-2',
|
||||
x: 0,
|
||||
y: 17,
|
||||
body: new SceneCanvasText({
|
||||
key: 'canvas-2',
|
||||
text: 'Panel inside row, server = $server',
|
||||
}),
|
||||
}),
|
||||
new SceneGridItem({
|
||||
key: 'griditem-3',
|
||||
x: 0,
|
||||
y: 25,
|
||||
body: new SceneCanvasText({
|
||||
key: 'canvas-3',
|
||||
text: 'Panel inside row, server = $server',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -180,8 +180,12 @@ function updateLayout(layout: SceneGridLayout, rows: SceneGridRow[], maxYOfRows:
|
||||
const diff = maxYOfRows - firstChildAfterY;
|
||||
|
||||
for (const child of childrenAfter) {
|
||||
if (child.state.y! < maxYOfRows) {
|
||||
child.setState({ y: child.state.y! + diff });
|
||||
child.setState({ y: child.state.y! + diff });
|
||||
|
||||
if (child instanceof SceneGridRow) {
|
||||
for (const rowChild of child.state.children) {
|
||||
rowChild.setState({ y: rowChild.state.y! + diff });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import {
|
||||
SceneComponentProps,
|
||||
SceneGridItem,
|
||||
SceneGridLayout,
|
||||
SceneGridRow,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
SceneQueryRunner,
|
||||
VizPanel,
|
||||
} from '@grafana/scenes';
|
||||
import { Icon, TextLink, useStyles2 } from '@grafana/ui';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
|
||||
import { ShowConfirmModalEvent } from 'app/types/events';
|
||||
|
||||
import { getDashboardSceneFor } from '../../utils/utils';
|
||||
import { DashboardScene } from '../DashboardScene';
|
||||
import { RowRepeaterBehavior } from '../RowRepeaterBehavior';
|
||||
|
||||
import { RowOptionsButton } from './RowOptionsButton';
|
||||
|
||||
export interface RowActionsState extends SceneObjectState {}
|
||||
|
||||
export class RowActions extends SceneObjectBase<RowActionsState> {
|
||||
private updateLayout(rowClone: SceneGridRow): void {
|
||||
const row = this.getParent();
|
||||
|
||||
const layout = this.getDashboard().state.body;
|
||||
|
||||
if (!(layout instanceof SceneGridLayout)) {
|
||||
throw new Error('Layout is not a SceneGridLayout');
|
||||
}
|
||||
|
||||
// remove the repeated rows
|
||||
const children = layout.state.children.filter((child) => !child.state.key?.startsWith(`${row.state.key}-clone-`));
|
||||
|
||||
// get the index to replace later
|
||||
const index = children.indexOf(row);
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error('Parent row not found in layout children');
|
||||
}
|
||||
|
||||
// replace the row with the clone
|
||||
layout.setState({
|
||||
children: [...children.slice(0, index), rowClone, ...children.slice(index + 1)],
|
||||
});
|
||||
}
|
||||
|
||||
public getParent(): SceneGridRow {
|
||||
if (!(this.parent instanceof SceneGridRow)) {
|
||||
throw new Error('RowActions must have a SceneGridRow parent');
|
||||
}
|
||||
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public getDashboard(): DashboardScene {
|
||||
return getDashboardSceneFor(this);
|
||||
}
|
||||
|
||||
public onUpdate = (title: string, repeat?: string | null): void => {
|
||||
const row = this.getParent();
|
||||
|
||||
// return early if there is no repeat
|
||||
if (!repeat) {
|
||||
const clone = row.clone();
|
||||
|
||||
// remove the row repeater behaviour, leave the rest
|
||||
clone.setState({
|
||||
title,
|
||||
$behaviors: row.state.$behaviors?.filter((b) => !(b instanceof RowRepeaterBehavior)) ?? [],
|
||||
});
|
||||
|
||||
this.updateLayout(clone);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const children = row.state.children.map((child) => child.clone());
|
||||
|
||||
const newBehaviour = new RowRepeaterBehavior({
|
||||
variableName: repeat,
|
||||
sources: children,
|
||||
});
|
||||
|
||||
// get rest of behaviors except the old row repeater, if any, and push new one
|
||||
const behaviors = row.state.$behaviors?.filter((b) => !(b instanceof RowRepeaterBehavior)) ?? [];
|
||||
behaviors.push(newBehaviour);
|
||||
|
||||
row.setState({
|
||||
title,
|
||||
$behaviors: behaviors,
|
||||
});
|
||||
|
||||
newBehaviour.activate();
|
||||
};
|
||||
|
||||
public onDelete = () => {
|
||||
appEvents.publish(
|
||||
new ShowConfirmModalEvent({
|
||||
title: 'Delete row',
|
||||
text: 'Are you sure you want to remove this row and all its panels?',
|
||||
altActionText: 'Delete row only',
|
||||
icon: 'trash-alt',
|
||||
onConfirm: () => {
|
||||
this.getDashboard().removeRow(this.getParent(), true);
|
||||
},
|
||||
onAltAction: () => {
|
||||
this.getDashboard().removeRow(this.getParent());
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
public getWarning = () => {
|
||||
const row = this.getParent();
|
||||
const gridItems = row.state.children;
|
||||
|
||||
const isAnyPanelUsingDashboardDS = gridItems.some((gridItem) => {
|
||||
if (!(gridItem instanceof SceneGridItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gridItem.state.body instanceof VizPanel && gridItem.state.body.state.$data instanceof SceneQueryRunner) {
|
||||
return gridItem.state.body.state.$data?.state.datasource?.uid === SHARED_DASHBOARD_QUERY;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (isAnyPanelUsingDashboardDS) {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
Panels in this row use the {SHARED_DASHBOARD_QUERY} data source. These panels will reference the panel in
|
||||
the original row, not the ones in the repeated rows.
|
||||
</p>
|
||||
<TextLink
|
||||
external
|
||||
href={
|
||||
'https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/create-dashboard/#configure-repeating-rows'
|
||||
}
|
||||
>
|
||||
Learn more
|
||||
</TextLink>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<RowActions>) => {
|
||||
const dashboard = model.getDashboard();
|
||||
const row = model.getParent();
|
||||
const { title } = row.useState();
|
||||
const { meta, isEditing } = dashboard.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const behaviour = row.state.$behaviors?.find((b) => b instanceof RowRepeaterBehavior);
|
||||
|
||||
return (
|
||||
<>
|
||||
{meta.canEdit && isEditing && (
|
||||
<>
|
||||
<div className={styles.rowActions}>
|
||||
<RowOptionsButton
|
||||
title={title}
|
||||
repeat={behaviour instanceof RowRepeaterBehavior ? behaviour.state.variableName : undefined}
|
||||
parent={dashboard}
|
||||
onUpdate={model.onUpdate}
|
||||
warning={model.getWarning()}
|
||||
/>
|
||||
<button type="button" onClick={model.onDelete} aria-label="Delete row">
|
||||
<Icon name="trash-alt" />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
rowActions: css({
|
||||
color: theme.colors.text.secondary,
|
||||
lineHeight: '27px',
|
||||
|
||||
button: {
|
||||
color: theme.colors.text.secondary,
|
||||
paddingLeft: theme.spacing(2),
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
|
||||
'&:hover': {
|
||||
color: theme.colors.text.maxContrast,
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
|
||||
import { SceneObject } from '@grafana/scenes';
|
||||
import { Icon, ModalsController } from '@grafana/ui';
|
||||
|
||||
import { OnRowOptionsUpdate } from './RowOptionsForm';
|
||||
import { RowOptionsModal } from './RowOptionsModal';
|
||||
|
||||
export interface RowOptionsButtonProps {
|
||||
title: string;
|
||||
repeat?: string;
|
||||
parent: SceneObject;
|
||||
onUpdate: OnRowOptionsUpdate;
|
||||
warning?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const RowOptionsButton = ({ repeat, title, parent, onUpdate, warning }: RowOptionsButtonProps) => {
|
||||
const onUpdateChange = (hideModal: () => void) => (title: string, repeat?: string | null) => {
|
||||
onUpdate(title, repeat);
|
||||
hideModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalsController>
|
||||
{({ showModal, hideModal }) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="pointer"
|
||||
aria-label="Row options"
|
||||
onClick={() => {
|
||||
showModal(RowOptionsModal, {
|
||||
title,
|
||||
repeat,
|
||||
parent,
|
||||
onDismiss: hideModal,
|
||||
onUpdate: onUpdateChange(hideModal),
|
||||
warning,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon name="cog" />
|
||||
</button>
|
||||
);
|
||||
}}
|
||||
</ModalsController>
|
||||
);
|
||||
};
|
||||
|
||||
RowOptionsButton.displayName = 'RowOptionsButton';
|
||||
@@ -0,0 +1,51 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { TestProvider } from 'test/helpers/TestProvider';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { DashboardScene } from '../DashboardScene';
|
||||
|
||||
import { RowOptionsForm } from './RowOptionsForm';
|
||||
|
||||
jest.mock('app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect', () => ({
|
||||
RepeatRowSelect2: () => <div />,
|
||||
}));
|
||||
describe('DashboardRow', () => {
|
||||
const scene = new DashboardScene({
|
||||
title: 'hello',
|
||||
uid: 'dash-1',
|
||||
meta: {
|
||||
canEdit: true,
|
||||
},
|
||||
});
|
||||
|
||||
it('Should show warning component when has warningMessage prop', () => {
|
||||
render(
|
||||
<TestProvider>
|
||||
<RowOptionsForm
|
||||
repeat={'3'}
|
||||
parent={scene}
|
||||
title=""
|
||||
onCancel={jest.fn()}
|
||||
onUpdate={jest.fn()}
|
||||
warning="a warning message"
|
||||
/>
|
||||
</TestProvider>
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(selectors.pages.Dashboard.Rows.Repeated.ConfigSection.warningMessage)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should not show warning component when does not have warningMessage prop', () => {
|
||||
render(
|
||||
<TestProvider>
|
||||
<RowOptionsForm repeat={'3'} parent={scene} title="" onCancel={jest.fn()} onUpdate={jest.fn()} />
|
||||
</TestProvider>
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId(selectors.pages.Dashboard.Rows.Repeated.ConfigSection.warningMessage)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { SceneObject } from '@grafana/scenes';
|
||||
import { Button, Field, Modal, Input, Alert } from '@grafana/ui';
|
||||
import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect';
|
||||
|
||||
export type OnRowOptionsUpdate = (title: string, repeat?: string | null) => void;
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
repeat?: string;
|
||||
parent: SceneObject;
|
||||
onUpdate: OnRowOptionsUpdate;
|
||||
onCancel: () => void;
|
||||
warning?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const RowOptionsForm = ({ repeat, title, parent, warning, onUpdate, onCancel }: Props) => {
|
||||
const [newRepeat, setNewRepeat] = useState<string | undefined>(repeat);
|
||||
const onChangeRepeat = useCallback((name?: string) => setNewRepeat(name), [setNewRepeat]);
|
||||
|
||||
const { handleSubmit, register } = useForm({
|
||||
defaultValues: {
|
||||
title,
|
||||
},
|
||||
});
|
||||
|
||||
const submit = (formData: { title: string }) => {
|
||||
onUpdate(formData.title, newRepeat);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(submit)}>
|
||||
<Field label="Title">
|
||||
<Input {...register('title')} type="text" />
|
||||
</Field>
|
||||
<Field label="Repeat for">
|
||||
<RepeatRowSelect2 parent={parent} repeat={newRepeat} onChange={onChangeRepeat} />
|
||||
</Field>
|
||||
{warning && (
|
||||
<Alert
|
||||
data-testid={selectors.pages.Dashboard.Rows.Repeated.ConfigSection.warningMessage}
|
||||
severity="warning"
|
||||
title=""
|
||||
topSpacing={3}
|
||||
bottomSpacing={0}
|
||||
>
|
||||
{warning}
|
||||
</Alert>
|
||||
)}
|
||||
<Modal.ButtonRow>
|
||||
<Button type="button" variant="secondary" onClick={onCancel} fill="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Update</Button>
|
||||
</Modal.ButtonRow>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { SceneObject } from '@grafana/scenes';
|
||||
import { Modal, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { OnRowOptionsUpdate, RowOptionsForm } from './RowOptionsForm';
|
||||
|
||||
export interface RowOptionsModalProps {
|
||||
title: string;
|
||||
repeat?: string;
|
||||
parent: SceneObject;
|
||||
warning?: React.ReactNode;
|
||||
onDismiss: () => void;
|
||||
onUpdate: OnRowOptionsUpdate;
|
||||
}
|
||||
|
||||
export const RowOptionsModal = ({ repeat, title, parent, onDismiss, onUpdate, warning }: RowOptionsModalProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<Modal isOpen={true} title="Row options" onDismiss={onDismiss} className={styles.modal}>
|
||||
<RowOptionsForm
|
||||
parent={parent}
|
||||
repeat={repeat}
|
||||
title={title}
|
||||
onCancel={onDismiss}
|
||||
onUpdate={onUpdate}
|
||||
warning={warning}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = () => ({
|
||||
modal: css({
|
||||
label: 'RowOptionsModal',
|
||||
width: '500px',
|
||||
}),
|
||||
});
|
||||
@@ -47,6 +47,7 @@ import { PanelNotices } from '../scene/PanelNotices';
|
||||
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
||||
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
||||
import { RowActions } from '../scene/row-actions/RowActions';
|
||||
import { setDashboardPanelContext } from '../scene/setDashboardPanelContext';
|
||||
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
|
||||
import { DashboardInteractions } from '../utils/interactions';
|
||||
@@ -211,6 +212,7 @@ function createRowFromPanelModel(row: PanelModel, content: SceneGridItemLike[]):
|
||||
isCollapsed: row.collapsed,
|
||||
children: children,
|
||||
$behaviors: behaviors,
|
||||
actions: new RowActions({}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
|
||||
import { panelMenuBehavior } from '../scene/PanelMenuBehavior';
|
||||
import { RowActions } from '../scene/row-actions/RowActions';
|
||||
|
||||
import { dashboardSceneGraph } from './dashboardSceneGraph';
|
||||
|
||||
@@ -233,6 +234,7 @@ export function getDefaultRow(dashboard: DashboardScene): SceneGridRow {
|
||||
return new SceneGridRow({
|
||||
key: getVizPanelKeyForPanelId(id),
|
||||
title: 'Row title',
|
||||
actions: new RowActions({}),
|
||||
y: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
import { DataLinksInlineEditor, Input, RadioButtonGroup, Select, Switch, TextArea } from '@grafana/ui';
|
||||
import { VizPanelManager, VizPanelManagerState } from 'app/features/dashboard-scene/panel-edit/VizPanelManager';
|
||||
import { VizPanelLinks } from 'app/features/dashboard-scene/scene/PanelLinks';
|
||||
@@ -176,8 +177,11 @@ export function getPanelFrameCategory(props: OptionPaneRenderProps): OptionsPane
|
||||
);
|
||||
}
|
||||
|
||||
export function getPanelFrameCategory2(panelManager: VizPanelManager): OptionsPaneCategoryDescriptor {
|
||||
const { panel } = panelManager.state;
|
||||
export function getPanelFrameCategory2(
|
||||
panelManager: VizPanelManager,
|
||||
panel: VizPanel,
|
||||
repeat?: string
|
||||
): OptionsPaneCategoryDescriptor {
|
||||
const descriptor = new OptionsPaneCategoryDescriptor({
|
||||
title: 'Panel options',
|
||||
id: 'Panel options',
|
||||
@@ -270,7 +274,8 @@ export function getPanelFrameCategory2(panelManager: VizPanelManager): OptionsPa
|
||||
return (
|
||||
<RepeatRowSelect2
|
||||
id="repeat-by-variable-select"
|
||||
panelManager={panelManager}
|
||||
parent={panel}
|
||||
repeat={repeat}
|
||||
onChange={(value?: string) => {
|
||||
const stateUpdate: Partial<VizPanelManagerState> = { repeat: value };
|
||||
if (value && !panelManager.state.repeatDirection) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { sceneGraph } from '@grafana/scenes';
|
||||
import { SceneObject, sceneGraph } from '@grafana/scenes';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { VizPanelManager } from 'app/features/dashboard-scene/panel-edit/VizPanelManager';
|
||||
import { useSelector } from 'app/types';
|
||||
|
||||
import { getLastKey, getVariablesByKey } from '../../../variables/state/selectors';
|
||||
@@ -45,14 +44,14 @@ export const RepeatRowSelect = ({ repeat, onChange, id }: Props) => {
|
||||
};
|
||||
|
||||
interface Props2 {
|
||||
panelManager: VizPanelManager;
|
||||
parent: SceneObject;
|
||||
repeat: string | undefined;
|
||||
id?: string;
|
||||
onChange: (name?: string) => void;
|
||||
}
|
||||
|
||||
export const RepeatRowSelect2 = ({ panelManager, id, onChange }: Props2) => {
|
||||
const { panel, repeat } = panelManager.useState();
|
||||
const sceneVars = useMemo(() => sceneGraph.getVariables(panel), [panel]);
|
||||
export const RepeatRowSelect2 = ({ parent, repeat, id, onChange }: Props2) => {
|
||||
const sceneVars = useMemo(() => sceneGraph.getVariables(parent), [parent]);
|
||||
const variables = sceneVars.useState().variables;
|
||||
|
||||
const variableOptions = useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user