mirror of
https://github.com/grafana/grafana.git
synced 2025-09-16 07:22:45 +08:00
Repeating: Minor refactoring and unification for DashboardGridItem and AutoGridItem (#109723)
* Repeating: Refactoring and unification * Update * update * fix e2e * fixes * Update * adjust e2e test --------- Co-authored-by: Sergej-Vlasov <sergej.s.vlasov@gmail.com>
This commit is contained in:
@ -35,12 +35,12 @@ test.describe(
|
|||||||
test('Can view solo repeated panel in scenes', async ({ page, selectors }) => {
|
test('Can view solo repeated panel in scenes', async ({ page, selectors }) => {
|
||||||
// open Panel Tests - Graph NG
|
// open Panel Tests - Graph NG
|
||||||
const soloPanelUrl = selectors.pages.SoloPanel.url(
|
const soloPanelUrl = selectors.pages.SoloPanel.url(
|
||||||
'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-0&__feature.dashboardSceneSolo=true'
|
'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-1&__feature.dashboardSceneSolo=true'
|
||||||
);
|
);
|
||||||
await page.goto(soloPanelUrl);
|
await page.goto(soloPanelUrl);
|
||||||
|
|
||||||
// Check that the panel title exists
|
// Check that the panel title exists
|
||||||
const panelTitle = page.getByTestId(selectors.components.Panels.Panel.title('server=A'));
|
const panelTitle = page.getByTestId(selectors.components.Panels.Panel.title('server=B'));
|
||||||
await expect(panelTitle).toBeVisible();
|
await expect(panelTitle).toBeVisible();
|
||||||
|
|
||||||
// Check that uplot-main-div does not exist
|
// Check that uplot-main-div does not exist
|
||||||
|
@ -25,10 +25,10 @@ describe('Solo Route', () => {
|
|||||||
it('Can view solo repeated panel in scenes', () => {
|
it('Can view solo repeated panel in scenes', () => {
|
||||||
// open Panel Tests - Graph NG
|
// open Panel Tests - Graph NG
|
||||||
e2e.pages.SoloPanel.visit(
|
e2e.pages.SoloPanel.visit(
|
||||||
'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-0&__feature.dashboardSceneSolo=true'
|
'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-1&__feature.dashboardSceneSolo=true'
|
||||||
);
|
);
|
||||||
|
|
||||||
e2e.components.Panels.Panel.title('server=A').should('exist');
|
e2e.components.Panels.Panel.title('server=B').should('exist');
|
||||||
cy.contains('uplot-main-div').should('not.exist');
|
cy.contains('uplot-main-div').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,10 +25,10 @@ describe('Solo Route', () => {
|
|||||||
it('Can view solo repeated panel in scenes', () => {
|
it('Can view solo repeated panel in scenes', () => {
|
||||||
// open Panel Tests - Graph NG
|
// open Panel Tests - Graph NG
|
||||||
e2e.pages.SoloPanel.visit(
|
e2e.pages.SoloPanel.visit(
|
||||||
'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-0&__feature.dashboardSceneSolo=true'
|
'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-1&__feature.dashboardSceneSolo=true'
|
||||||
);
|
);
|
||||||
|
|
||||||
e2e.components.Panels.Panel.title('server=A').should('exist');
|
e2e.components.Panels.Panel.title('server=B').should('exist');
|
||||||
cy.contains('uplot-main-div').should('not.exist');
|
cy.contains('uplot-main-div').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,21 +3,18 @@ import React from 'react';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CustomVariable,
|
CustomVariable,
|
||||||
LocalValueVariable,
|
|
||||||
MultiValueVariable,
|
MultiValueVariable,
|
||||||
sceneGraph,
|
sceneGraph,
|
||||||
SceneObjectBase,
|
SceneObjectBase,
|
||||||
SceneObjectState,
|
SceneObjectState,
|
||||||
SceneVariableSet,
|
|
||||||
VariableDependencyConfig,
|
VariableDependencyConfig,
|
||||||
VariableValueSingle,
|
VariableValueSingle,
|
||||||
VizPanel,
|
VizPanel,
|
||||||
VizPanelState,
|
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||||
|
|
||||||
import { ConditionalRendering } from '../../conditional-rendering/ConditionalRendering';
|
import { ConditionalRendering } from '../../conditional-rendering/ConditionalRendering';
|
||||||
import { getCloneKey } from '../../utils/clone';
|
import { getCloneKey, getLocalVariableValueSet } from '../../utils/clone';
|
||||||
import { getMultiVariableValues } from '../../utils/utils';
|
import { getMultiVariableValues } from '../../utils/utils';
|
||||||
import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
|
import { scrollCanvasElementIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
|
||||||
import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
|
import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
|
||||||
@ -113,21 +110,19 @@ export class AutoGridItem extends SceneObjectBase<AutoGridItemState> implements
|
|||||||
|
|
||||||
const variableValues = values.length ? values : emptyVariablePlaceholderOption.values;
|
const variableValues = values.length ? values : emptyVariablePlaceholderOption.values;
|
||||||
const variableTexts = texts.length ? texts : emptyVariablePlaceholderOption.texts;
|
const variableTexts = texts.length ? texts : emptyVariablePlaceholderOption.texts;
|
||||||
|
|
||||||
|
// Loop through variable values and create repeats
|
||||||
for (let index = 0; index < variableValues.length; index++) {
|
for (let index = 0; index < variableValues.length; index++) {
|
||||||
const cloneState: Partial<VizPanelState> = {
|
const isSource = index === 0;
|
||||||
$variables: new SceneVariableSet({
|
const clone = isSource
|
||||||
variables: [
|
? panelToRepeat
|
||||||
new LocalValueVariable({
|
: panelToRepeat.clone({ key: getCloneKey(panelToRepeat.state.key!, index) });
|
||||||
name: variable.state.name,
|
|
||||||
value: variableValues[index],
|
clone.setState({ $variables: getLocalVariableValueSet(variable, variableValues[index], variableTexts[index]) });
|
||||||
text: String(variableTexts[index]),
|
|
||||||
}),
|
if (index > 0) {
|
||||||
],
|
repeatedPanels.push(clone);
|
||||||
}),
|
}
|
||||||
key: getCloneKey(panelToRepeat.state.key!, index),
|
|
||||||
};
|
|
||||||
const clone = panelToRepeat.clone(cloneState);
|
|
||||||
repeatedPanels.push(clone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ repeatedPanels });
|
this.setState({ repeatedPanels });
|
||||||
@ -136,6 +131,10 @@ export class AutoGridItem extends SceneObjectBase<AutoGridItemState> implements
|
|||||||
this.publishEvent(new DashboardRepeatsProcessedEvent({ source: this }), true);
|
this.publishEvent(new DashboardRepeatsProcessedEvent({ source: this }), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPanelCount() {
|
||||||
|
return (this.state.repeatedPanels?.length ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
public setRepeatByVariable(variableName: string | undefined) {
|
public setRepeatByVariable(variableName: string | undefined) {
|
||||||
const stateUpdate: Partial<AutoGridItemState> = { variableName };
|
const stateUpdate: Partial<AutoGridItemState> = { variableName };
|
||||||
|
|
||||||
@ -172,13 +171,6 @@ export class AutoGridItem extends SceneObjectBase<AutoGridItemState> implements
|
|||||||
if (!this.state.variableName) {
|
if (!this.state.variableName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((this.state.repeatedPanels?.length ?? 0) > 1) {
|
|
||||||
this.state.body.setState({
|
|
||||||
$variables: this.state.repeatedPanels![0].state.$variables?.clone(),
|
|
||||||
$data: this.state.repeatedPanels![0].state.$data?.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public editingCompleted(withChanges: boolean) {
|
public editingCompleted(withChanges: boolean) {
|
||||||
|
@ -13,7 +13,7 @@ import { AutoGridItem } from './AutoGridItem';
|
|||||||
import { DRAGGED_ITEM_HEIGHT, DRAGGED_ITEM_LEFT, DRAGGED_ITEM_TOP, DRAGGED_ITEM_WIDTH } from './const';
|
import { DRAGGED_ITEM_HEIGHT, DRAGGED_ITEM_LEFT, DRAGGED_ITEM_TOP, DRAGGED_ITEM_WIDTH } from './const';
|
||||||
|
|
||||||
export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem>) {
|
export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem>) {
|
||||||
const { body, repeatedPanels, key } = model.useState();
|
const { body, repeatedPanels = [], key } = model.useState();
|
||||||
const { draggingKey } = model.getParentGrid().useState();
|
const { draggingKey } = model.getParentGrid().useState();
|
||||||
const { isEditing, preload } = useDashboardState(model);
|
const { isEditing, preload } = useDashboardState(model);
|
||||||
const [isConditionallyHidden, conditionalRenderingClass, conditionalRenderingOverlay] =
|
const [isConditionallyHidden, conditionalRenderingClass, conditionalRenderingOverlay] =
|
||||||
@ -69,20 +69,19 @@ export function AutoGridItemRenderer({ model }: SceneComponentProps<AutoGridItem
|
|||||||
const isDragging = !!draggingKey;
|
const isDragging = !!draggingKey;
|
||||||
const isDragged = draggingKey === key;
|
const isDragged = draggingKey === key;
|
||||||
|
|
||||||
return repeatedPanels ? (
|
return (
|
||||||
<>
|
<>
|
||||||
{repeatedPanels.map((item, index) => (
|
<Wrapper item={body} addDndContainer={true} key={body.state.key!} isDragged={isDragged} isDragging={isDragging} />
|
||||||
|
{repeatedPanels.map((item) => (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
item={item}
|
item={item}
|
||||||
addDndContainer={index === 0}
|
addDndContainer={false}
|
||||||
key={item.state.key!}
|
key={item.state.key!}
|
||||||
isDragged={isDragged}
|
isDragged={isDragged}
|
||||||
isDragging={isDragging}
|
isDragging={isDragging}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
<Wrapper item={body} addDndContainer key={body.state.key!} isDragged={isDragged} isDragging={isDragging} />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,17 +32,17 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
|
|
||||||
activateFullSceneTree(scene);
|
activateFullSceneTree(scene);
|
||||||
|
|
||||||
expect(repeater.state.repeatedPanels?.length).toBe(5);
|
expect(repeater.state.repeatedPanels?.length).toBe(4);
|
||||||
|
|
||||||
const panel1 = repeater.state.repeatedPanels![0];
|
const panel1 = repeater.state.body;
|
||||||
const panel2 = repeater.state.repeatedPanels![1];
|
const panel2 = repeater.state.repeatedPanels![0];
|
||||||
|
|
||||||
// Panels should have scoped variables
|
// Panels should have scoped variables
|
||||||
expect(panel1.state.$variables?.state.variables[0].getValue()).toBe('1');
|
expect(panel1.state.$variables?.state.variables[0].getValue()).toBe('1');
|
||||||
expect(panel1.state.$variables?.state.variables[0].getValueText?.()).toBe('A');
|
expect(panel1.state.$variables?.state.variables[0].getValueText?.()).toBe('A');
|
||||||
expect(panel2.state.$variables?.state.variables[0].getValue()).toBe('2');
|
expect(panel2.state.$variables?.state.variables[0].getValue()).toBe('2');
|
||||||
|
|
||||||
expect(isInCloneChain(panel1.state.key!)).toBe(false);
|
expect(panel1.state.key).toBe('panel-1');
|
||||||
expect(isInCloneChain(panel2.state.key!)).toBe(true);
|
expect(isInCloneChain(panel2.state.key!)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 10));
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
expect(repeater.state.repeatedPanels?.length).toBe(5);
|
expect(repeater.state.repeatedPanels?.length).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should pass isMulti/includeAll values if variable is multi variable and has them set', async () => {
|
it('Should pass isMulti/includeAll values if variable is multi variable and has them set', async () => {
|
||||||
@ -67,7 +67,7 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 10));
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
expect(repeater.state.repeatedPanels?.length).toBe(5);
|
expect(repeater.state.repeatedPanels?.length).toBe(4);
|
||||||
|
|
||||||
// LocalValueVariableState is not exposed, so we build this type casting
|
// LocalValueVariableState is not exposed, so we build this type casting
|
||||||
const variableState = repeater.state.repeatedPanels![0].state.$variables?.state.variables[0].state as {
|
const variableState = repeater.state.repeatedPanels![0].state.$variables?.state.variables[0].state as {
|
||||||
@ -79,18 +79,6 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
expect(variableState.includeAll).toBe(true);
|
expect(variableState.includeAll).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should display a panel when there are no options', async () => {
|
|
||||||
const { scene, repeater } = buildPanelRepeaterScene({ variableQueryTime: 1, numberOfOptions: 0 });
|
|
||||||
|
|
||||||
activateFullSceneTree(scene);
|
|
||||||
|
|
||||||
expect(repeater.state.repeatedPanels?.length).toBe(0);
|
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 100));
|
|
||||||
|
|
||||||
expect(repeater.state.repeatedPanels?.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should redo the repeat when editing panel and then returning to dashboard', async () => {
|
it('Should redo the repeat when editing panel and then returning to dashboard', async () => {
|
||||||
const panel = new DashboardGridItem({
|
const panel = new DashboardGridItem({
|
||||||
variableName: 'server',
|
variableName: 'server',
|
||||||
@ -130,7 +118,7 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 10));
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
expect(panel.state.repeatedPanels?.length).toBe(5);
|
expect(panel.state.repeatedPanels?.length).toBe(4);
|
||||||
|
|
||||||
const vizPanel = panel.state.body as VizPanel;
|
const vizPanel = panel.state.body as VizPanel;
|
||||||
|
|
||||||
@ -150,7 +138,7 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 10));
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
expect(panel.state.repeatedPanels?.length).toBe(5);
|
expect(panel.state.repeatedPanels?.length).toBe(4);
|
||||||
expect((panel.state.repeatedPanels![0] as VizPanel).state.title).toBe('Changed');
|
expect((panel.state.repeatedPanels![0] as VizPanel).state.title).toBe('Changed');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -203,7 +191,7 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 10));
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
expect(panel.state.repeatedPanels?.length).toBe(5);
|
expect(panel.state.repeatedPanels?.length).toBe(4);
|
||||||
|
|
||||||
const vizPanel = panel.state.body as VizPanel;
|
const vizPanel = panel.state.body as VizPanel;
|
||||||
|
|
||||||
@ -226,27 +214,10 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
await new Promise((r) => setTimeout(r, 10));
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
expect(performRepeatMock).toHaveBeenCalledTimes(1); // only for the edited panel
|
expect(performRepeatMock).toHaveBeenCalledTimes(1); // only for the edited panel
|
||||||
expect(panel.state.repeatedPanels?.length).toBe(5);
|
expect(panel.state.repeatedPanels?.length).toBe(4);
|
||||||
expect((panel.state.repeatedPanels![0] as VizPanel).state.title).toBe('Changed');
|
expect((panel.state.repeatedPanels![0] as VizPanel).state.title).toBe('Changed');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should display a panel when there are variable errors', () => {
|
|
||||||
const { scene, repeater } = buildPanelRepeaterScene({
|
|
||||||
variableQueryTime: 0,
|
|
||||||
numberOfOptions: 0,
|
|
||||||
throwError: 'Error',
|
|
||||||
});
|
|
||||||
|
|
||||||
// we expect console.error when variable encounters an error
|
|
||||||
const origError = console.error;
|
|
||||||
console.error = jest.fn();
|
|
||||||
|
|
||||||
activateFullSceneTree(scene);
|
|
||||||
|
|
||||||
expect(repeater.state.repeatedPanels?.length).toBe(1);
|
|
||||||
console.error = origError;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should display a panel when there are variable errors async query', async () => {
|
it('Should display a panel when there are variable errors async query', async () => {
|
||||||
const { scene, repeater } = buildPanelRepeaterScene({
|
const { scene, repeater } = buildPanelRepeaterScene({
|
||||||
variableQueryTime: 1,
|
variableQueryTime: 1,
|
||||||
@ -262,7 +233,7 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 10));
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
|
||||||
expect(repeater.state.repeatedPanels?.length).toBe(1);
|
expect(repeater.state.body.state.$variables?.state.variables[0].getValue()).toBe('');
|
||||||
console.error = origError;
|
console.error = origError;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -346,14 +317,14 @@ describe('PanelRepeaterGridItem', () => {
|
|||||||
|
|
||||||
variable.changeValueTo(['1', '3'], ['A', 'C']);
|
variable.changeValueTo(['1', '3'], ['A', 'C']);
|
||||||
|
|
||||||
expect(repeater.state.repeatedPanels?.length).toBe(2);
|
expect(repeater.state.repeatedPanels?.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should fall back to default variable if specified variable cannot be found', () => {
|
it('Should fall back to default variable if specified variable cannot be found', () => {
|
||||||
const { scene, repeater } = buildPanelRepeaterScene({ variableQueryTime: 0 });
|
const { scene, repeater } = buildPanelRepeaterScene({ variableQueryTime: 0 });
|
||||||
scene.setState({ $variables: undefined });
|
scene.setState({ $variables: undefined });
|
||||||
activateFullSceneTree(scene);
|
activateFullSceneTree(scene);
|
||||||
expect(repeater.state.repeatedPanels?.[0].state.$variables?.state.variables[0].state.name).toBe(
|
expect(repeater.state.body.state.$variables?.state.variables[0].state.name).toBe(
|
||||||
'_____default_sys_repeat_var_____'
|
'_____default_sys_repeat_var_____'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -6,20 +6,17 @@ import {
|
|||||||
VizPanel,
|
VizPanel,
|
||||||
SceneObjectBase,
|
SceneObjectBase,
|
||||||
SceneGridLayout,
|
SceneGridLayout,
|
||||||
SceneVariableSet,
|
|
||||||
SceneGridItemStateLike,
|
SceneGridItemStateLike,
|
||||||
SceneGridItemLike,
|
SceneGridItemLike,
|
||||||
sceneGraph,
|
sceneGraph,
|
||||||
MultiValueVariable,
|
MultiValueVariable,
|
||||||
LocalValueVariable,
|
|
||||||
CustomVariable,
|
CustomVariable,
|
||||||
VizPanelState,
|
|
||||||
VariableValueSingle,
|
VariableValueSingle,
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { GRID_COLUMN_COUNT } from 'app/core/constants';
|
import { GRID_COLUMN_COUNT } from 'app/core/constants';
|
||||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||||
|
|
||||||
import { getCloneKey } from '../../utils/clone';
|
import { getCloneKey, getLocalVariableValueSet } from '../../utils/clone';
|
||||||
import { getMultiVariableValues } from '../../utils/utils';
|
import { getMultiVariableValues } from '../../utils/utils';
|
||||||
import { scrollCanvasElementIntoView, scrollIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
|
import { scrollCanvasElementIntoView, scrollIntoView } from '../layouts-shared/scrollCanvasElementIntoView';
|
||||||
import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
|
import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
|
||||||
@ -86,13 +83,12 @@ export class DashboardGridItem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemCount = this.state.repeatedPanels?.length ?? 1;
|
|
||||||
const stateChange: Partial<DashboardGridItemState> = {};
|
const stateChange: Partial<DashboardGridItemState> = {};
|
||||||
|
|
||||||
if (this.getRepeatDirection() === 'v') {
|
if (this.getRepeatDirection() === 'v') {
|
||||||
stateChange.itemHeight = Math.ceil(newState.height! / itemCount);
|
stateChange.itemHeight = Math.ceil(newState.height! / this.getPanelCount());
|
||||||
} else {
|
} else {
|
||||||
const rowCount = Math.ceil(itemCount / this.getMaxPerRow());
|
const rowCount = Math.ceil(this.getPanelCount() / this.getMaxPerRow());
|
||||||
stateChange.itemHeight = Math.ceil(newState.height! / rowCount);
|
stateChange.itemHeight = Math.ceil(newState.height! / rowCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,6 +97,10 @@ export class DashboardGridItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPanelCount() {
|
||||||
|
return (this.state.repeatedPanels?.length ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
public getClassName(): string {
|
public getClassName(): string {
|
||||||
return this.state.variableName ? 'panel-repeater-grid-item' : '';
|
return this.state.variableName ? 'panel-repeater-grid-item' : '';
|
||||||
}
|
}
|
||||||
@ -117,13 +117,6 @@ export class DashboardGridItem
|
|||||||
if (!this.state.variableName) {
|
if (!this.state.variableName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.repeatedPanels?.length ?? 0 > 1) {
|
|
||||||
this.state.body.setState({
|
|
||||||
$variables: this.state.repeatedPanels![0].state.$variables?.clone(),
|
|
||||||
$data: this.state.repeatedPanels![0].state.$data?.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public editingCompleted(withChanges: boolean) {
|
public editingCompleted(withChanges: boolean) {
|
||||||
@ -177,22 +170,16 @@ export class DashboardGridItem
|
|||||||
|
|
||||||
// Loop through variable values and create repeats
|
// Loop through variable values and create repeats
|
||||||
for (let index = 0; index < variableValues.length; index++) {
|
for (let index = 0; index < variableValues.length; index++) {
|
||||||
const cloneState: Partial<VizPanelState> = {
|
const isSource = index === 0;
|
||||||
$variables: new SceneVariableSet({
|
const clone = isSource
|
||||||
variables: [
|
? panelToRepeat
|
||||||
new LocalValueVariable({
|
: panelToRepeat.clone({ key: getCloneKey(panelToRepeat.state.key!, index) });
|
||||||
name: variable.state.name,
|
|
||||||
value: variableValues[index],
|
clone.setState({ $variables: getLocalVariableValueSet(variable, variableValues[index], variableTexts[index]) });
|
||||||
text: String(variableTexts[index]),
|
|
||||||
isMulti: variable.state.isMulti,
|
if (index > 0) {
|
||||||
includeAll: variable.state.includeAll,
|
repeatedPanels.push(clone);
|
||||||
}),
|
}
|
||||||
],
|
|
||||||
}),
|
|
||||||
key: getCloneKey(panelToRepeat.state.key!, index),
|
|
||||||
};
|
|
||||||
const clone = panelToRepeat.clone(cloneState);
|
|
||||||
repeatedPanels.push(clone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const direction = this.getRepeatDirection();
|
const direction = this.getRepeatDirection();
|
||||||
@ -200,12 +187,13 @@ export class DashboardGridItem
|
|||||||
const itemHeight = this.state.itemHeight ?? 10;
|
const itemHeight = this.state.itemHeight ?? 10;
|
||||||
const prevHeight = this.state.height;
|
const prevHeight = this.state.height;
|
||||||
const maxPerRow = this.getMaxPerRow();
|
const maxPerRow = this.getMaxPerRow();
|
||||||
|
const panelCount = repeatedPanels.length + 1; // +1 for the source panel
|
||||||
|
|
||||||
if (direction === 'h') {
|
if (direction === 'h') {
|
||||||
const rowCount = Math.ceil(repeatedPanels.length / maxPerRow);
|
const rowCount = Math.ceil(panelCount / maxPerRow);
|
||||||
stateChange.height = rowCount * itemHeight;
|
stateChange.height = rowCount * itemHeight;
|
||||||
} else {
|
} else {
|
||||||
stateChange.height = repeatedPanels.length * itemHeight;
|
stateChange.height = panelCount * itemHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(stateChange);
|
this.setState(stateChange);
|
||||||
|
@ -2,32 +2,33 @@ import { css } from '@emotion/css';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { SceneComponentProps, VizPanel } from '@grafana/scenes';
|
import { SceneComponentProps } from '@grafana/scenes';
|
||||||
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
|
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
|
||||||
|
|
||||||
import { DashboardGridItem, RepeatDirection } from './DashboardGridItem';
|
import { DashboardGridItem, RepeatDirection } from './DashboardGridItem';
|
||||||
|
|
||||||
export function DashboardGridItemRenderer({ model }: SceneComponentProps<DashboardGridItem>) {
|
export function DashboardGridItemRenderer({ model }: SceneComponentProps<DashboardGridItem>) {
|
||||||
const { repeatedPanels, itemHeight, variableName, body } = model.useState();
|
const { repeatedPanels = [], itemHeight, variableName, body } = model.useState();
|
||||||
const itemCount = repeatedPanels?.length ?? 0;
|
const layoutStyle = useLayoutStyle(
|
||||||
const layoutStyle = useLayoutStyle(model.getRepeatDirection(), itemCount, model.getMaxPerRow(), itemHeight ?? 10);
|
model.getRepeatDirection(),
|
||||||
|
model.getPanelCount(),
|
||||||
|
model.getMaxPerRow(),
|
||||||
|
itemHeight ?? 10
|
||||||
|
);
|
||||||
|
|
||||||
if (!variableName) {
|
if (!variableName) {
|
||||||
if (body instanceof VizPanel) {
|
return (
|
||||||
return (
|
<div className={panelWrapper} ref={model.containerRef}>
|
||||||
<div className={panelWrapper} ref={model.containerRef}>
|
<body.Component model={body} key={body.state.key} />
|
||||||
<body.Component model={body} key={body.state.key} />
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!repeatedPanels) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={layoutStyle} ref={model.containerRef}>
|
<div className={layoutStyle} ref={model.containerRef}>
|
||||||
|
<div className={panelWrapper} key={body.state.key}>
|
||||||
|
<body.Component model={body} key={body.state.key} />
|
||||||
|
</div>
|
||||||
{repeatedPanels.map((panel) => (
|
{repeatedPanels.map((panel) => (
|
||||||
<div className={panelWrapper} key={panel.state.key}>
|
<div className={panelWrapper} key={panel.state.key}>
|
||||||
<panel.Component model={panel} key={panel.state.key} />
|
<panel.Component model={panel} key={panel.state.key} />
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LocalValueVariable,
|
|
||||||
MultiValueVariable,
|
MultiValueVariable,
|
||||||
sceneGraph,
|
sceneGraph,
|
||||||
SceneGridItemLike,
|
SceneGridItemLike,
|
||||||
@ -9,7 +8,6 @@ import {
|
|||||||
SceneGridRow,
|
SceneGridRow,
|
||||||
SceneObjectBase,
|
SceneObjectBase,
|
||||||
SceneObjectState,
|
SceneObjectState,
|
||||||
SceneVariableSet,
|
|
||||||
VariableDependencyConfig,
|
VariableDependencyConfig,
|
||||||
VariableValueSingle,
|
VariableValueSingle,
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
@ -22,6 +20,7 @@ import {
|
|||||||
getCloneKey,
|
getCloneKey,
|
||||||
isClonedKey,
|
isClonedKey,
|
||||||
getOriginalKey,
|
getOriginalKey,
|
||||||
|
getLocalVariableValueSet,
|
||||||
} from '../../utils/clone';
|
} from '../../utils/clone';
|
||||||
import { getMultiVariableValues } from '../../utils/utils';
|
import { getMultiVariableValues } from '../../utils/utils';
|
||||||
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
|
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
|
||||||
@ -173,17 +172,7 @@ export class RowRepeaterBehavior extends SceneObjectBase<RowRepeaterBehaviorStat
|
|||||||
|
|
||||||
rowClone.setState({
|
rowClone.setState({
|
||||||
key: rowCloneKey,
|
key: rowCloneKey,
|
||||||
$variables: new SceneVariableSet({
|
$variables: getLocalVariableValueSet(variable, variableValues[rowIndex], variableTexts[rowIndex]),
|
||||||
variables: [
|
|
||||||
new LocalValueVariable({
|
|
||||||
name: this.state.variableName,
|
|
||||||
value: variableValues[rowIndex],
|
|
||||||
text: String(variableTexts[rowIndex]),
|
|
||||||
isMulti: variable.state.isMulti,
|
|
||||||
includeAll: variable.state.includeAll,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import {
|
import { MultiValueVariable, sceneGraph, VariableValueSingle } from '@grafana/scenes';
|
||||||
MultiValueVariable,
|
|
||||||
SceneVariableSet,
|
|
||||||
LocalValueVariable,
|
|
||||||
sceneGraph,
|
|
||||||
VariableValueSingle,
|
|
||||||
} from '@grafana/scenes';
|
|
||||||
import { Spinner } from '@grafana/ui';
|
import { Spinner } from '@grafana/ui';
|
||||||
|
|
||||||
import { DashboardStateChangedEvent } from '../../edit-pane/shared';
|
import { DashboardStateChangedEvent } from '../../edit-pane/shared';
|
||||||
import { getCloneKey } from '../../utils/clone';
|
import { getCloneKey, getLocalVariableValueSet } from '../../utils/clone';
|
||||||
import { dashboardLog, getMultiVariableValues } from '../../utils/utils';
|
import { dashboardLog, getMultiVariableValues } from '../../utils/utils';
|
||||||
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
|
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
|
||||||
|
|
||||||
@ -109,17 +103,7 @@ export function performRowRepeats(variable: MultiValueVariable, row: RowItem, co
|
|||||||
|
|
||||||
rowClone.setState({
|
rowClone.setState({
|
||||||
key: rowCloneKey,
|
key: rowCloneKey,
|
||||||
$variables: new SceneVariableSet({
|
$variables: getLocalVariableValueSet(variable, variableValues[rowIndex], variableTexts[rowIndex]),
|
||||||
variables: [
|
|
||||||
new LocalValueVariable({
|
|
||||||
name: variable.state.name,
|
|
||||||
value: variableValues[rowIndex],
|
|
||||||
text: String(variableTexts[rowIndex]),
|
|
||||||
isMulti: variable.state.isMulti,
|
|
||||||
includeAll: variable.state.includeAll,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
layout,
|
layout,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,17 +3,11 @@ import { isEqual } from 'lodash';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { t } from '@grafana/i18n';
|
import { t } from '@grafana/i18n';
|
||||||
import {
|
import { MultiValueVariable, sceneGraph, VariableValueSingle } from '@grafana/scenes';
|
||||||
MultiValueVariable,
|
|
||||||
SceneVariableSet,
|
|
||||||
LocalValueVariable,
|
|
||||||
sceneGraph,
|
|
||||||
VariableValueSingle,
|
|
||||||
} from '@grafana/scenes';
|
|
||||||
import { Spinner, Tooltip, useStyles2 } from '@grafana/ui';
|
import { Spinner, Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { DashboardStateChangedEvent } from '../../edit-pane/shared';
|
import { DashboardStateChangedEvent } from '../../edit-pane/shared';
|
||||||
import { getCloneKey } from '../../utils/clone';
|
import { getCloneKey, getLocalVariableValueSet } from '../../utils/clone';
|
||||||
import { dashboardLog, getMultiVariableValues } from '../../utils/utils';
|
import { dashboardLog, getMultiVariableValues } from '../../utils/utils';
|
||||||
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
|
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
|
||||||
|
|
||||||
@ -164,17 +158,7 @@ export function createTabRepeats({
|
|||||||
|
|
||||||
tabClone.setState({
|
tabClone.setState({
|
||||||
key: tabCloneKey,
|
key: tabCloneKey,
|
||||||
$variables: new SceneVariableSet({
|
$variables: getLocalVariableValueSet(variable, variableValues[tabIndex], variableTexts[tabIndex]),
|
||||||
variables: [
|
|
||||||
new LocalValueVariable({
|
|
||||||
name: variable.state.name,
|
|
||||||
value: variableValues[tabIndex],
|
|
||||||
text: String(variableTexts[tabIndex]),
|
|
||||||
isMulti: variable.state.isMulti,
|
|
||||||
includeAll: variable.state.includeAll,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
layout,
|
layout,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -792,7 +792,7 @@ describe('transformSceneToSaveModel', () => {
|
|||||||
|
|
||||||
activateFullSceneTree(scene);
|
activateFullSceneTree(scene);
|
||||||
|
|
||||||
expect(repeater.state.repeatedPanels?.length).toBe(2);
|
expect(repeater.state.repeatedPanels?.length).toBe(1);
|
||||||
const result = panelRepeaterToPanels(repeater, true);
|
const result = panelRepeaterToPanels(repeater, true);
|
||||||
|
|
||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(2);
|
||||||
|
@ -329,48 +329,46 @@ export function panelRepeaterToPanels(repeater: DashboardGridItem, isSnapshot =
|
|||||||
return [vizPanelToPanel(repeater.state.body, { x, y, w, h }, isSnapshot)];
|
return [vizPanelToPanel(repeater.state.body, { x, y, w, h }, isSnapshot)];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repeater.state.repeatedPanels) {
|
const vizPanels = [repeater.state.body, ...(repeater.state.repeatedPanels ?? [])];
|
||||||
const { h, w, columnCount } = calculateGridItemDimensions(repeater);
|
|
||||||
const panels = repeater.state.repeatedPanels!.map((panel, index) => {
|
|
||||||
let x = 0,
|
|
||||||
y = 0;
|
|
||||||
if (repeater.state.repeatDirection === 'v') {
|
|
||||||
x = repeater.state.x!;
|
|
||||||
y = index * h;
|
|
||||||
} else {
|
|
||||||
x = (index % columnCount) * w;
|
|
||||||
y = repeater.state.y! + Math.floor(index / columnCount) * h;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gridPos = { x, y, w, h };
|
const { h, w, columnCount } = calculateGridItemDimensions(repeater);
|
||||||
|
const panels = vizPanels.map((panel, index) => {
|
||||||
|
let x = 0,
|
||||||
|
y = 0;
|
||||||
|
if (repeater.state.repeatDirection === 'v') {
|
||||||
|
x = repeater.state.x!;
|
||||||
|
y = index * h;
|
||||||
|
} else {
|
||||||
|
x = (index % columnCount) * w;
|
||||||
|
y = repeater.state.y! + Math.floor(index / columnCount) * h;
|
||||||
|
}
|
||||||
|
|
||||||
const localVariable = panel.state.$variables!.getByName(repeater.state.variableName!) as LocalValueVariable;
|
const gridPos = { x, y, w, h };
|
||||||
|
|
||||||
const result: Panel = {
|
const localVariable = panel.state.$variables!.getByName(repeater.state.variableName!) as LocalValueVariable;
|
||||||
id: getPanelIdForVizPanel(panel),
|
|
||||||
type: panel.state.pluginId,
|
const result: Panel = {
|
||||||
title: panel.state.title,
|
id: getPanelIdForVizPanel(panel),
|
||||||
gridPos,
|
type: panel.state.pluginId,
|
||||||
options: panel.state.options,
|
title: panel.state.title,
|
||||||
fieldConfig: (panel.state.fieldConfig as FieldConfigSource) ?? { defaults: {}, overrides: [] },
|
gridPos,
|
||||||
transformations: [],
|
options: panel.state.options,
|
||||||
transparent: panel.state.displayMode === 'transparent',
|
fieldConfig: (panel.state.fieldConfig as FieldConfigSource) ?? { defaults: {}, overrides: [] },
|
||||||
// @ts-expect-error scopedVars are runtime only properties, not part of the persisted Dashboardmodel
|
transformations: [],
|
||||||
scopedVars: {
|
transparent: panel.state.displayMode === 'transparent',
|
||||||
[repeater.state.variableName!]: {
|
// @ts-expect-error scopedVars are runtime only properties, not part of the persisted Dashboardmodel
|
||||||
text: localVariable?.state.text,
|
scopedVars: {
|
||||||
value: localVariable?.state.value,
|
[repeater.state.variableName!]: {
|
||||||
},
|
text: localVariable?.state.text,
|
||||||
|
value: localVariable?.state.value,
|
||||||
},
|
},
|
||||||
...vizPanelDataToPanel(panel, isSnapshot),
|
},
|
||||||
};
|
...vizPanelDataToPanel(panel, isSnapshot),
|
||||||
return result;
|
};
|
||||||
});
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
return panels;
|
return panels;
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { SceneObject } from '@grafana/scenes';
|
import {
|
||||||
|
LocalValueVariable,
|
||||||
|
MultiValueVariableState,
|
||||||
|
SceneObject,
|
||||||
|
SceneVariable,
|
||||||
|
SceneVariableSet,
|
||||||
|
VariableValueSingle,
|
||||||
|
} from '@grafana/scenes';
|
||||||
|
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
|
|
||||||
@ -100,3 +107,21 @@ export function useHasClonedParents(scene: SceneObject): boolean {
|
|||||||
|
|
||||||
return useHasClonedParents(scene.parent);
|
return useHasClonedParents(scene.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLocalVariableValueSet(
|
||||||
|
variable: SceneVariable<MultiValueVariableState>,
|
||||||
|
value: VariableValueSingle,
|
||||||
|
text: VariableValueSingle
|
||||||
|
): SceneVariableSet {
|
||||||
|
return new SceneVariableSet({
|
||||||
|
variables: [
|
||||||
|
new LocalValueVariable({
|
||||||
|
name: variable.state.name,
|
||||||
|
value,
|
||||||
|
text,
|
||||||
|
isMulti: variable.state.isMulti,
|
||||||
|
includeAll: variable.state.includeAll,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -143,6 +143,7 @@ export function buildPanelRepeaterScene(options: SceneOptions, source?: VizPanel
|
|||||||
new VizPanel({
|
new VizPanel({
|
||||||
title: 'Panel $server',
|
title: 'Panel $server',
|
||||||
pluginId: 'timeseries',
|
pluginId: 'timeseries',
|
||||||
|
key: 'panel-1',
|
||||||
}),
|
}),
|
||||||
x: options.x || 0,
|
x: options.x || 0,
|
||||||
y: options.y || 0,
|
y: options.y || 0,
|
||||||
|
Reference in New Issue
Block a user