mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 12:22:05 +08:00
Dynamic dashboards: Repeat responsive grid items (#101291)
* repeat responsive grid items * fix stuff from feedback * Simplify repeat dependency, fix locale stuff
This commit is contained in:
@ -574,6 +574,7 @@ ResponsiveGridLayoutItemKind: {
|
||||
|
||||
ResponsiveGridLayoutItemSpec: {
|
||||
element: ElementReference
|
||||
repeat?: ResponsiveGridRepeatOptions
|
||||
}
|
||||
|
||||
TabsLayoutKind: {
|
||||
|
@ -867,6 +867,7 @@ export const defaultResponsiveGridLayoutItemKind = (): ResponsiveGridLayoutItemK
|
||||
|
||||
export interface ResponsiveGridLayoutItemSpec {
|
||||
element: ElementReference;
|
||||
repeat?: ResponsiveGridRepeatOptions;
|
||||
}
|
||||
|
||||
export const defaultResponsiveGridLayoutItemSpec = (): ResponsiveGridLayoutItemSpec => ({
|
||||
|
@ -1,7 +1,24 @@
|
||||
import { SceneObjectState, VizPanel, SceneObjectBase } from '@grafana/scenes';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import {
|
||||
SceneObjectState,
|
||||
VizPanel,
|
||||
SceneObjectBase,
|
||||
sceneGraph,
|
||||
CustomVariable,
|
||||
MultiValueVariable,
|
||||
VariableValueSingle,
|
||||
VizPanelState,
|
||||
SceneVariableSet,
|
||||
LocalValueVariable,
|
||||
VariableDependencyConfig,
|
||||
} from '@grafana/scenes';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
|
||||
import { getCloneKey } from '../../utils/clone';
|
||||
import { getMultiVariableValues } from '../../utils/utils';
|
||||
import { DashboardLayoutItem } from '../types/DashboardLayoutItem';
|
||||
import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent';
|
||||
|
||||
import { getOptions } from './ResponsiveGridItemEditor';
|
||||
import { ResponsiveGridItemRenderer } from './ResponsiveGridItemRenderer';
|
||||
@ -9,13 +26,30 @@ import { ResponsiveGridItemRenderer } from './ResponsiveGridItemRenderer';
|
||||
export interface ResponsiveGridItemState extends SceneObjectState {
|
||||
body: VizPanel;
|
||||
hideWhenNoData?: boolean;
|
||||
repeatedPanels?: VizPanel[];
|
||||
variableName?: string;
|
||||
}
|
||||
|
||||
export class ResponsiveGridItem extends SceneObjectBase<ResponsiveGridItemState> implements DashboardLayoutItem {
|
||||
public static Component = ResponsiveGridItemRenderer;
|
||||
|
||||
private _prevRepeatValues?: VariableValueSingle[];
|
||||
protected _variableDependency = new VariableDependencyConfig(this, {
|
||||
variableNames: this.state.variableName ? [this.state.variableName] : [],
|
||||
onVariableUpdateCompleted: () => this.performRepeat(),
|
||||
});
|
||||
public readonly isDashboardLayoutItem = true;
|
||||
|
||||
public constructor(state: ResponsiveGridItemState) {
|
||||
super(state);
|
||||
this.addActivationHandler(() => this._activationHandler());
|
||||
}
|
||||
|
||||
private _activationHandler() {
|
||||
if (this.state.variableName) {
|
||||
this.performRepeat();
|
||||
}
|
||||
}
|
||||
|
||||
public getOptions(): OptionsPaneCategoryDescriptor {
|
||||
return getOptions(this);
|
||||
}
|
||||
@ -23,4 +57,78 @@ export class ResponsiveGridItem extends SceneObjectBase<ResponsiveGridItemState>
|
||||
public toggleHideWhenNoData() {
|
||||
this.setState({ hideWhenNoData: !this.state.hideWhenNoData });
|
||||
}
|
||||
|
||||
public performRepeat() {
|
||||
if (!this.state.variableName || sceneGraph.hasVariableDependencyInLoadingState(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const variable =
|
||||
sceneGraph.lookupVariable(this.state.variableName, this) ??
|
||||
new CustomVariable({
|
||||
name: '_____default_sys_repeat_var_____',
|
||||
options: [],
|
||||
value: '',
|
||||
text: '',
|
||||
query: 'A',
|
||||
});
|
||||
|
||||
if (!(variable instanceof MultiValueVariable)) {
|
||||
console.error('DashboardGridItem: Variable is not a MultiValueVariable');
|
||||
return;
|
||||
}
|
||||
|
||||
const { values, texts } = getMultiVariableValues(variable);
|
||||
|
||||
if (isEqual(this._prevRepeatValues, values)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const panelToRepeat = this.state.body;
|
||||
const repeatedPanels: VizPanel[] = [];
|
||||
|
||||
// when variable has no options (due to error or similar) it will not render any panels at all
|
||||
// adding a placeholder in this case so that there is at least empty panel that can display error
|
||||
const emptyVariablePlaceholderOption = {
|
||||
values: [''],
|
||||
texts: variable.hasAllValue() ? ['All'] : ['None'],
|
||||
};
|
||||
|
||||
const variableValues = values.length ? values : emptyVariablePlaceholderOption.values;
|
||||
const variableTexts = texts.length ? texts : emptyVariablePlaceholderOption.texts;
|
||||
for (let index = 0; index < variableValues.length; index++) {
|
||||
const cloneState: Partial<VizPanelState> = {
|
||||
$variables: new SceneVariableSet({
|
||||
variables: [
|
||||
new LocalValueVariable({
|
||||
name: variable.state.name,
|
||||
value: variableValues[index],
|
||||
text: String(variableTexts[index]),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
key: getCloneKey(panelToRepeat.state.key!, index),
|
||||
};
|
||||
const clone = panelToRepeat.clone(cloneState);
|
||||
repeatedPanels.push(clone);
|
||||
}
|
||||
|
||||
this.setState({ repeatedPanels });
|
||||
this._prevRepeatValues = values;
|
||||
|
||||
this.publishEvent(new DashboardRepeatsProcessedEvent({ source: this }), true);
|
||||
}
|
||||
|
||||
public setRepeatByVariable(variableName: string | undefined) {
|
||||
const stateUpdate: Partial<ResponsiveGridItemState> = { variableName };
|
||||
|
||||
if (this.state.body.state.$variables) {
|
||||
this.state.body.setState({ $variables: undefined });
|
||||
}
|
||||
|
||||
this._variableDependency.setVariableNames(variableName ? [variableName] : []);
|
||||
|
||||
this.setState(stateUpdate);
|
||||
this.performRepeat();
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { Switch } from '@grafana/ui';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect';
|
||||
|
||||
import { ResponsiveGridItem } from './ResponsiveGridItem';
|
||||
|
||||
@ -19,6 +20,17 @@ export function getOptions(model: ResponsiveGridItem): OptionsPaneCategoryDescri
|
||||
})
|
||||
);
|
||||
|
||||
category.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: t('dashboard.responsive-layout.item-options.repeat.variable.title', 'Repeat by variable'),
|
||||
description: t(
|
||||
'dashboard.responsive-layout.item-options.repeat.variable.description',
|
||||
'Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.'
|
||||
),
|
||||
render: () => <RepeatByOption item={model} />,
|
||||
})
|
||||
);
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
@ -27,3 +39,16 @@ function GridItemNoDataToggle({ item }: { item: ResponsiveGridItem }) {
|
||||
|
||||
return <Switch value={hideWhenNoData} id="hide-when-no-data" onChange={() => item.toggleHideWhenNoData()} />;
|
||||
}
|
||||
|
||||
function RepeatByOption({ item }: { item: ResponsiveGridItem }) {
|
||||
const { variableName } = item.useState();
|
||||
|
||||
return (
|
||||
<RepeatRowSelect2
|
||||
id="repeat-by-variable-select"
|
||||
sceneContext={item}
|
||||
repeat={variableName}
|
||||
onChange={(value?: string) => item.setRepeatByVariable(value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -9,7 +9,15 @@ export function ResponsiveGridItemRenderer({ model }: SceneComponentProps<Respon
|
||||
const { body } = model.useState();
|
||||
const style = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
return model.state.repeatedPanels ? (
|
||||
<>
|
||||
{model.state.repeatedPanels.map((item) => (
|
||||
<div className={cx(style.wrapper)} key={item.state.key}>
|
||||
<item.Component model={item} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className={cx(style.wrapper)}>
|
||||
<body.Component model={body} />
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SceneCSSGridLayout } from '@grafana/scenes';
|
||||
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
import { DashboardV2Spec, ResponsiveGridLayoutItemKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0';
|
||||
|
||||
import { ResponsiveGridItem } from '../../scene/layout-responsive-grid/ResponsiveGridItem';
|
||||
import { ResponsiveGridLayoutManager } from '../../scene/layout-responsive-grid/ResponsiveGridLayoutManager';
|
||||
@ -21,7 +21,7 @@ export class ResponsiveGridLayoutSerializer implements LayoutManagerSerializer {
|
||||
if (!(child instanceof ResponsiveGridItem)) {
|
||||
throw new Error('Expected ResponsiveGridItem');
|
||||
}
|
||||
return {
|
||||
const layoutItem: ResponsiveGridLayoutItemKind = {
|
||||
kind: 'ResponsiveGridLayoutItem',
|
||||
spec: {
|
||||
element: {
|
||||
@ -30,6 +30,15 @@ export class ResponsiveGridLayoutSerializer implements LayoutManagerSerializer {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (child.state.variableName) {
|
||||
layoutItem.spec.repeat = {
|
||||
mode: 'variable',
|
||||
value: child.state.variableName,
|
||||
};
|
||||
}
|
||||
|
||||
return layoutItem;
|
||||
}),
|
||||
},
|
||||
};
|
||||
@ -51,6 +60,7 @@ export class ResponsiveGridLayoutSerializer implements LayoutManagerSerializer {
|
||||
return new ResponsiveGridItem({
|
||||
key: getGridItemKeyForPanelId(panel.spec.id),
|
||||
body: buildVizPanel(panel),
|
||||
variableName: item.spec.repeat?.value,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1184,6 +1184,12 @@
|
||||
"description": "CSS layout that adjusts to the available space",
|
||||
"item-options": {
|
||||
"hide-no-data": "Hide when no data",
|
||||
"repeat": {
|
||||
"variable": {
|
||||
"description": "Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.",
|
||||
"title": "Repeat by variable"
|
||||
}
|
||||
},
|
||||
"title": "Layout options"
|
||||
},
|
||||
"name": "Responsive grid",
|
||||
|
@ -1184,6 +1184,12 @@
|
||||
"description": "CŜŜ ľäyőūŧ ŧĥäŧ äđĵūşŧş ŧő ŧĥę äväįľäþľę şpäčę",
|
||||
"item-options": {
|
||||
"hide-no-data": "Ħįđę ŵĥęʼn ʼnő đäŧä",
|
||||
"repeat": {
|
||||
"variable": {
|
||||
"description": "Ŗępęäŧ ŧĥįş päʼnęľ ƒőř ęäčĥ väľūę įʼn ŧĥę şęľęčŧęđ väřįäþľę. Ŧĥįş įş ʼnőŧ vįşįþľę ŵĥįľę įʼn ęđįŧ mőđę. Ÿőū ʼnęęđ ŧő ģő þäčĸ ŧő đäşĥþőäřđ äʼnđ ŧĥęʼn ūpđäŧę ŧĥę väřįäþľę őř řęľőäđ ŧĥę đäşĥþőäřđ.",
|
||||
"title": "Ŗępęäŧ þy väřįäþľę"
|
||||
}
|
||||
},
|
||||
"title": "Ŀäyőūŧ őpŧįőʼnş"
|
||||
},
|
||||
"name": "Ŗęşpőʼnşįvę ģřįđ",
|
||||
|
Reference in New Issue
Block a user