mirror of https://github.com/grafana/grafana
DashbboardScene: RowRepeaterBehavior (#74505)
* Repeating rows start * working * Progress * Progress * Update * up scenes lib * Update * Progress * restore url sync * Progress * Fixes and tests * Update * Adds tests and code to remove repeats from save model * Update * Fix testpull/74650/head^2
parent
fb367bf91d
commit
97d568e60a
@ -1,334 +0,0 @@ |
||||
{ |
||||
"annotations": { |
||||
"list": [ |
||||
{ |
||||
"builtIn": 1, |
||||
"datasource": "-- Grafana --", |
||||
"enable": true, |
||||
"hide": true, |
||||
"iconColor": "rgba(0, 211, 255, 1)", |
||||
"name": "Annotations & Alerts", |
||||
"target": { |
||||
"limit": 100, |
||||
"matchAny": false, |
||||
"tags": [], |
||||
"type": "dashboard" |
||||
}, |
||||
"type": "dashboard" |
||||
} |
||||
] |
||||
}, |
||||
"editable": true, |
||||
"fiscalYearStartMonth": 0, |
||||
"graphTooltip": 0, |
||||
"iteration": 1640181176989, |
||||
"links": [], |
||||
"liveNow": false, |
||||
"panels": [ |
||||
{ |
||||
"collapsed": false, |
||||
"gridPos": { |
||||
"h": 1, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 0 |
||||
}, |
||||
"id": 2, |
||||
"panels": [], |
||||
"repeat": "row", |
||||
"title": "Row title $row", |
||||
"type": "row" |
||||
}, |
||||
{ |
||||
"datasource": { |
||||
"type": "testdata" |
||||
}, |
||||
"description": "", |
||||
"fieldConfig": { |
||||
"defaults": { |
||||
"color": { |
||||
"mode": "palette-classic" |
||||
}, |
||||
"custom": { |
||||
"axisLabel": "", |
||||
"axisPlacement": "auto", |
||||
"barAlignment": 0, |
||||
"drawStyle": "line", |
||||
"fillOpacity": 0, |
||||
"gradientMode": "none", |
||||
"hideFrom": { |
||||
"legend": false, |
||||
"tooltip": false, |
||||
"viz": false |
||||
}, |
||||
"lineInterpolation": "linear", |
||||
"lineWidth": 1, |
||||
"pointSize": 5, |
||||
"scaleDistribution": { |
||||
"type": "linear" |
||||
}, |
||||
"showPoints": "auto", |
||||
"spanNulls": false, |
||||
"stacking": { |
||||
"group": "A", |
||||
"mode": "none" |
||||
}, |
||||
"thresholdsStyle": { |
||||
"mode": "off" |
||||
} |
||||
}, |
||||
"mappings": [], |
||||
"thresholds": { |
||||
"mode": "absolute", |
||||
"steps": [ |
||||
{ |
||||
"color": "green" |
||||
}, |
||||
{ |
||||
"color": "red", |
||||
"value": 80 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"overrides": [] |
||||
}, |
||||
"gridPos": { |
||||
"h": 8, |
||||
"w": 8, |
||||
"x": 0, |
||||
"y": 1 |
||||
}, |
||||
"id": 4, |
||||
"options": { |
||||
"legend": { |
||||
"calcs": [], |
||||
"displayMode": "list", |
||||
"placement": "bottom" |
||||
}, |
||||
"tooltip": { |
||||
"mode": "single", |
||||
"sort": "none" |
||||
} |
||||
}, |
||||
"title": "Panel Title", |
||||
"type": "timeseries" |
||||
}, |
||||
{ |
||||
"datasource": { |
||||
"type": "testdata" |
||||
}, |
||||
"description": "", |
||||
"fieldConfig": { |
||||
"defaults": { |
||||
"color": { |
||||
"mode": "palette-classic" |
||||
}, |
||||
"custom": { |
||||
"axisLabel": "", |
||||
"axisPlacement": "auto", |
||||
"barAlignment": 0, |
||||
"drawStyle": "line", |
||||
"fillOpacity": 0, |
||||
"gradientMode": "none", |
||||
"hideFrom": { |
||||
"legend": false, |
||||
"tooltip": false, |
||||
"viz": false |
||||
}, |
||||
"lineInterpolation": "linear", |
||||
"lineWidth": 1, |
||||
"pointSize": 5, |
||||
"scaleDistribution": { |
||||
"type": "linear" |
||||
}, |
||||
"showPoints": "auto", |
||||
"spanNulls": false, |
||||
"stacking": { |
||||
"group": "A", |
||||
"mode": "none" |
||||
}, |
||||
"thresholdsStyle": { |
||||
"mode": "off" |
||||
} |
||||
}, |
||||
"mappings": [], |
||||
"thresholds": { |
||||
"mode": "absolute", |
||||
"steps": [ |
||||
{ |
||||
"color": "green" |
||||
}, |
||||
{ |
||||
"color": "red", |
||||
"value": 80 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"overrides": [] |
||||
}, |
||||
"gridPos": { |
||||
"h": 8, |
||||
"w": 8, |
||||
"x": 8, |
||||
"y": 1 |
||||
}, |
||||
"id": 9, |
||||
"options": { |
||||
"legend": { |
||||
"calcs": [], |
||||
"displayMode": "list", |
||||
"placement": "bottom" |
||||
}, |
||||
"tooltip": { |
||||
"mode": "single", |
||||
"sort": "none" |
||||
} |
||||
}, |
||||
"repeat": "vertical", |
||||
"repeatDirection": "v", |
||||
"title": "Vertical repeating $vertical", |
||||
"type": "timeseries" |
||||
} |
||||
], |
||||
"schemaVersion": 34, |
||||
"tags": [], |
||||
"templating": { |
||||
"list": [ |
||||
{ |
||||
"current": { |
||||
"selected": true, |
||||
"text": [ |
||||
"All" |
||||
], |
||||
"value": [ |
||||
"$__all" |
||||
] |
||||
}, |
||||
"hide": 0, |
||||
"includeAll": true, |
||||
"multi": true, |
||||
"name": "vertical", |
||||
"options": [ |
||||
{ |
||||
"selected": true, |
||||
"text": "All", |
||||
"value": "$__all" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "1", |
||||
"value": "1" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "2", |
||||
"value": "2" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "3", |
||||
"value": "3" |
||||
} |
||||
], |
||||
"query": "1,2,3", |
||||
"queryValue": "", |
||||
"skipUrlSync": false, |
||||
"type": "custom" |
||||
}, |
||||
{ |
||||
"current": { |
||||
"selected": true, |
||||
"text": [ |
||||
"All" |
||||
], |
||||
"value": [ |
||||
"$__all" |
||||
] |
||||
}, |
||||
"hide": 0, |
||||
"includeAll": true, |
||||
"multi": true, |
||||
"name": "horizontal", |
||||
"options": [ |
||||
{ |
||||
"selected": true, |
||||
"text": "All", |
||||
"value": "$__all" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "1", |
||||
"value": "1" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "2", |
||||
"value": "2" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "3", |
||||
"value": "3" |
||||
} |
||||
], |
||||
"query": "1,2,3", |
||||
"queryValue": "", |
||||
"skipUrlSync": false, |
||||
"type": "custom" |
||||
}, |
||||
{ |
||||
"current": { |
||||
"selected": true, |
||||
"text": [ |
||||
"All" |
||||
], |
||||
"value": [ |
||||
"$__all" |
||||
] |
||||
}, |
||||
"hide": 0, |
||||
"includeAll": true, |
||||
"multi": true, |
||||
"name": "row", |
||||
"options": [ |
||||
{ |
||||
"selected": true, |
||||
"text": "All", |
||||
"value": "$__all" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "1", |
||||
"value": "1" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "2", |
||||
"value": "2" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "3", |
||||
"value": "3" |
||||
} |
||||
], |
||||
"query": "1,2,3", |
||||
"queryValue": "", |
||||
"skipUrlSync": false, |
||||
"type": "custom" |
||||
} |
||||
] |
||||
}, |
||||
"time": { |
||||
"from": "now-6h", |
||||
"to": "now" |
||||
}, |
||||
"timepicker": {}, |
||||
"timezone": "utc", |
||||
"title": "Repeating a row with a non-repeating panel and vertical repeating panel", |
||||
"uid": "7lS-ojt7z", |
||||
"version": 2, |
||||
"weekStart": "" |
||||
} |
@ -1,257 +0,0 @@ |
||||
{ |
||||
"annotations": { |
||||
"list": [ |
||||
{ |
||||
"builtIn": 1, |
||||
"datasource": "-- Grafana --", |
||||
"enable": true, |
||||
"hide": true, |
||||
"iconColor": "rgba(0, 211, 255, 1)", |
||||
"name": "Annotations & Alerts", |
||||
"target": { |
||||
"limit": 100, |
||||
"matchAny": false, |
||||
"tags": [], |
||||
"type": "dashboard" |
||||
}, |
||||
"type": "dashboard" |
||||
} |
||||
] |
||||
}, |
||||
"editable": true, |
||||
"fiscalYearStartMonth": 0, |
||||
"graphTooltip": 0, |
||||
"iteration": 1640181195825, |
||||
"links": [], |
||||
"liveNow": false, |
||||
"panels": [ |
||||
{ |
||||
"collapsed": false, |
||||
"gridPos": { |
||||
"h": 1, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 0 |
||||
}, |
||||
"id": 2, |
||||
"panels": [], |
||||
"repeat": "row", |
||||
"title": "Row title $row", |
||||
"type": "row" |
||||
}, |
||||
{ |
||||
"datasource": { |
||||
"type": "testdata" |
||||
}, |
||||
"description": "", |
||||
"fieldConfig": { |
||||
"defaults": { |
||||
"color": { |
||||
"mode": "palette-classic" |
||||
}, |
||||
"custom": { |
||||
"axisLabel": "", |
||||
"axisPlacement": "auto", |
||||
"barAlignment": 0, |
||||
"drawStyle": "line", |
||||
"fillOpacity": 0, |
||||
"gradientMode": "none", |
||||
"hideFrom": { |
||||
"legend": false, |
||||
"tooltip": false, |
||||
"viz": false |
||||
}, |
||||
"lineInterpolation": "linear", |
||||
"lineWidth": 1, |
||||
"pointSize": 5, |
||||
"scaleDistribution": { |
||||
"type": "linear" |
||||
}, |
||||
"showPoints": "auto", |
||||
"spanNulls": false, |
||||
"stacking": { |
||||
"group": "A", |
||||
"mode": "none" |
||||
}, |
||||
"thresholdsStyle": { |
||||
"mode": "off" |
||||
} |
||||
}, |
||||
"mappings": [], |
||||
"thresholds": { |
||||
"mode": "absolute", |
||||
"steps": [ |
||||
{ |
||||
"color": "green" |
||||
}, |
||||
{ |
||||
"color": "red", |
||||
"value": 80 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"overrides": [] |
||||
}, |
||||
"gridPos": { |
||||
"h": 8, |
||||
"w": 8, |
||||
"x": 0, |
||||
"y": 1 |
||||
}, |
||||
"id": 4, |
||||
"options": { |
||||
"legend": { |
||||
"calcs": [], |
||||
"displayMode": "list", |
||||
"placement": "bottom" |
||||
}, |
||||
"tooltip": { |
||||
"mode": "single", |
||||
"sort": "none" |
||||
} |
||||
}, |
||||
"title": "Panel Title", |
||||
"type": "timeseries" |
||||
} |
||||
], |
||||
"schemaVersion": 34, |
||||
"tags": [], |
||||
"templating": { |
||||
"list": [ |
||||
{ |
||||
"current": { |
||||
"selected": true, |
||||
"text": [ |
||||
"All" |
||||
], |
||||
"value": [ |
||||
"$__all" |
||||
] |
||||
}, |
||||
"hide": 0, |
||||
"includeAll": true, |
||||
"multi": true, |
||||
"name": "vertical", |
||||
"options": [ |
||||
{ |
||||
"selected": true, |
||||
"text": "All", |
||||
"value": "$__all" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "1", |
||||
"value": "1" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "2", |
||||
"value": "2" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "3", |
||||
"value": "3" |
||||
} |
||||
], |
||||
"query": "1,2,3", |
||||
"queryValue": "", |
||||
"skipUrlSync": false, |
||||
"type": "custom" |
||||
}, |
||||
{ |
||||
"current": { |
||||
"selected": true, |
||||
"text": [ |
||||
"All" |
||||
], |
||||
"value": [ |
||||
"$__all" |
||||
] |
||||
}, |
||||
"hide": 0, |
||||
"includeAll": true, |
||||
"multi": true, |
||||
"name": "horizontal", |
||||
"options": [ |
||||
{ |
||||
"selected": true, |
||||
"text": "All", |
||||
"value": "$__all" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "1", |
||||
"value": "1" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "2", |
||||
"value": "2" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "3", |
||||
"value": "3" |
||||
} |
||||
], |
||||
"query": "1,2,3", |
||||
"queryValue": "", |
||||
"skipUrlSync": false, |
||||
"type": "custom" |
||||
}, |
||||
{ |
||||
"current": { |
||||
"selected": true, |
||||
"text": [ |
||||
"All" |
||||
], |
||||
"value": [ |
||||
"$__all" |
||||
] |
||||
}, |
||||
"hide": 0, |
||||
"includeAll": true, |
||||
"multi": true, |
||||
"name": "row", |
||||
"options": [ |
||||
{ |
||||
"selected": true, |
||||
"text": "All", |
||||
"value": "$__all" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "1", |
||||
"value": "1" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "2", |
||||
"value": "2" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "3", |
||||
"value": "3" |
||||
} |
||||
], |
||||
"query": "1,2,3", |
||||
"queryValue": "", |
||||
"skipUrlSync": false, |
||||
"type": "custom" |
||||
} |
||||
] |
||||
}, |
||||
"time": { |
||||
"from": "now-6h", |
||||
"to": "now" |
||||
}, |
||||
"timepicker": {}, |
||||
"timezone": "utc", |
||||
"title": "Repeating a row with a non-repeating panel", |
||||
"uid": "ZzyTojpnz", |
||||
"version": 3, |
||||
"weekStart": "" |
||||
} |
@ -1,41 +0,0 @@ |
||||
import { e2e } from '../utils'; |
||||
const PAGE_UNDER_TEST = 'k3PEoCpnk/repeating-a-row-with-a-non-repeating-panel-and-horizontal-repeating-panel'; |
||||
const DASHBOARD_NAME = 'Repeating a row with a non-repeating panel and horizontal repeating panel'; |
||||
|
||||
describe('Repeating a row with repeated panels and a non-repeating panel', () => { |
||||
beforeEach(() => { |
||||
e2e.flows.login('admin', 'admin'); |
||||
}); |
||||
|
||||
it('should be able to collapse and expand a repeated row without losing panels', () => { |
||||
e2e.flows.openDashboard({ uid: PAGE_UNDER_TEST }); |
||||
e2e().contains(DASHBOARD_NAME).should('be.visible'); |
||||
|
||||
const panelsToCheck = [ |
||||
'Row 2 non-repeating panel', |
||||
'Row 2 repeating panel 1', |
||||
'Row 2 repeating panel 2', |
||||
'Row 2 repeating panel 3', |
||||
]; |
||||
|
||||
// Collapse Row 1 first so the Row 2 panels all fit on the screen
|
||||
e2e.components.DashboardRow.title('Row 1').click(); |
||||
|
||||
// Rows are expanded by default, so check that all panels are visible
|
||||
panelsToCheck.forEach((title) => { |
||||
e2e.components.Panels.Panel.title(title).should('be.visible'); |
||||
}); |
||||
|
||||
// Collapse the row and check panels are no longer visible
|
||||
e2e.components.DashboardRow.title('Row 2').click(); |
||||
panelsToCheck.forEach((title) => { |
||||
e2e.components.Panels.Panel.title(title).should('not.exist'); |
||||
}); |
||||
|
||||
// Expand the row and check all panels are visible again
|
||||
e2e.components.DashboardRow.title('Row 2').click(); |
||||
panelsToCheck.forEach((title) => { |
||||
e2e.components.Panels.Panel.title(title).should('be.visible'); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,144 @@ |
||||
import { |
||||
EmbeddedScene, |
||||
SceneCanvasText, |
||||
SceneGridItem, |
||||
SceneGridLayout, |
||||
SceneGridRow, |
||||
SceneTimeRange, |
||||
SceneVariableSet, |
||||
TestVariable, |
||||
} from '@grafana/scenes'; |
||||
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants'; |
||||
|
||||
import { activateFullSceneTree } from '../utils/test-utils'; |
||||
|
||||
import { RepeatDirection } from './PanelRepeaterGridItem'; |
||||
import { RowRepeaterBehavior } from './RowRepeaterBehavior'; |
||||
|
||||
describe('RowRepeaterBehavior', () => { |
||||
describe('Given scene with variable with 5 values', () => { |
||||
let scene: EmbeddedScene, grid: SceneGridLayout; |
||||
|
||||
beforeEach(async () => { |
||||
({ scene, grid } = buildScene({ variableQueryTime: 0 })); |
||||
activateFullSceneTree(scene); |
||||
await new Promise((r) => setTimeout(r, 1)); |
||||
}); |
||||
|
||||
it('Should repeat row', () => { |
||||
// Verify that panel above row remains
|
||||
expect(grid.state.children[0]).toBeInstanceOf(SceneGridItem); |
||||
// Verify that first row still has repeat behavior
|
||||
const row1 = grid.state.children[1] as SceneGridRow; |
||||
expect(row1.state.$behaviors?.[0]).toBeInstanceOf(RowRepeaterBehavior); |
||||
expect(row1.state.$variables!.state.variables[0].getValue()).toBe('1'); |
||||
|
||||
const row2 = grid.state.children[2] as SceneGridRow; |
||||
expect(row2.state.$variables!.state.variables[0].getValueText?.()).toBe('B'); |
||||
|
||||
// Should give repeated panels unique keys
|
||||
const gridItem = row2.state.children[0] as SceneGridItem; |
||||
expect(gridItem.state.body?.state.key).toBe('canvas-1-row-1'); |
||||
}); |
||||
|
||||
it('Should push row at the bottom down', () => { |
||||
// Should push row at the bottom down
|
||||
const rowAtTheBottom = grid.state.children[6] as SceneGridRow; |
||||
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); |
||||
}); |
||||
|
||||
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; |
||||
variable.changeValueTo(['2', '3']); |
||||
|
||||
await new Promise((r) => setTimeout(r, 1)); |
||||
|
||||
// should now only have 2 repeated rows (and the panel above + the row at the bottom)
|
||||
expect(grid.state.children.length).toBe(4); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
interface SceneOptions { |
||||
variableQueryTime: number; |
||||
maxPerRow?: number; |
||||
itemHeight?: number; |
||||
repeatDirection?: RepeatDirection; |
||||
} |
||||
|
||||
function buildScene(options: SceneOptions) { |
||||
const grid = new SceneGridLayout({ |
||||
children: [ |
||||
new SceneGridItem({ |
||||
x: 0, |
||||
y: 0, |
||||
width: 24, |
||||
height: 10, |
||||
body: new SceneCanvasText({ |
||||
text: 'Panel above row', |
||||
}), |
||||
}), |
||||
new SceneGridRow({ |
||||
x: 0, |
||||
y: 10, |
||||
width: 24, |
||||
height: 1, |
||||
$behaviors: [ |
||||
new RowRepeaterBehavior({ |
||||
variableName: 'server', |
||||
sources: [ |
||||
new SceneGridItem({ |
||||
x: 0, |
||||
y: 11, |
||||
width: 24, |
||||
height: 5, |
||||
body: new SceneCanvasText({ |
||||
key: 'canvas-1', |
||||
text: 'Panel inside repeated row, server = $server', |
||||
}), |
||||
}), |
||||
], |
||||
}), |
||||
], |
||||
}), |
||||
new SceneGridRow({ |
||||
x: 0, |
||||
y: 16, |
||||
width: 24, |
||||
height: 5, |
||||
title: 'Row at the bottom', |
||||
}), |
||||
], |
||||
}); |
||||
|
||||
const scene = new EmbeddedScene({ |
||||
$timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }), |
||||
$variables: new SceneVariableSet({ |
||||
variables: [ |
||||
new TestVariable({ |
||||
name: 'server', |
||||
query: 'A.*', |
||||
value: ALL_VARIABLE_VALUE, |
||||
text: ALL_VARIABLE_TEXT, |
||||
isMulti: true, |
||||
includeAll: true, |
||||
delayMs: options.variableQueryTime, |
||||
optionsToReturn: [ |
||||
{ label: 'A', value: '1' }, |
||||
{ label: 'B', value: '2' }, |
||||
{ label: 'C', value: '3' }, |
||||
{ label: 'D', value: '4' }, |
||||
{ label: 'E', value: '5' }, |
||||
], |
||||
}), |
||||
], |
||||
}), |
||||
body: grid, |
||||
}); |
||||
|
||||
return { scene, grid }; |
||||
} |
@ -0,0 +1,215 @@ |
||||
import { |
||||
LocalValueVariable, |
||||
MultiValueVariable, |
||||
sceneGraph, |
||||
SceneGridItemLike, |
||||
SceneGridLayout, |
||||
SceneGridRow, |
||||
SceneObjectBase, |
||||
SceneObjectState, |
||||
SceneVariable, |
||||
SceneVariableSet, |
||||
VariableDependencyConfig, |
||||
VariableValueSingle, |
||||
} from '@grafana/scenes'; |
||||
|
||||
import { getMultiVariableValues } from '../utils/utils'; |
||||
|
||||
interface RowRepeaterBehaviorState extends SceneObjectState { |
||||
variableName: string; |
||||
sources: SceneGridItemLike[]; |
||||
} |
||||
|
||||
/** |
||||
* This behavior will run an effect function when specified variables change |
||||
*/ |
||||
|
||||
export class RowRepeaterBehavior extends SceneObjectBase<RowRepeaterBehaviorState> { |
||||
protected _variableDependency = new VariableDependencyConfig(this, { |
||||
variableNames: [this.state.variableName], |
||||
onVariableUpdatesCompleted: this._onVariableChanged.bind(this), |
||||
}); |
||||
|
||||
private _isWaitingForVariables = false; |
||||
|
||||
public constructor(state: RowRepeaterBehaviorState) { |
||||
super(state); |
||||
|
||||
this.addActivationHandler(() => this._activationHandler()); |
||||
} |
||||
|
||||
private _activationHandler() { |
||||
// If we our variable is ready we can process repeats on activation
|
||||
if (sceneGraph.hasVariableDependencyInLoadingState(this)) { |
||||
this._isWaitingForVariables = true; |
||||
} else { |
||||
this._performRepeat(); |
||||
} |
||||
} |
||||
|
||||
private _onVariableChanged(changedVariables: Set<SceneVariable>, dependencyChanged: boolean): void { |
||||
if (dependencyChanged) { |
||||
this._performRepeat(); |
||||
return; |
||||
} |
||||
|
||||
// If we are waiting for variables and the variable is no longer loading then we are ready to repeat as well
|
||||
if (this._isWaitingForVariables && !sceneGraph.hasVariableDependencyInLoadingState(this)) { |
||||
this._isWaitingForVariables = false; |
||||
this._performRepeat(); |
||||
} |
||||
} |
||||
|
||||
private _performRepeat() { |
||||
const variable = sceneGraph.lookupVariable(this.state.variableName, this.parent?.parent!); |
||||
|
||||
if (!variable) { |
||||
console.error('RepeatedRowBehavior: Variable not found'); |
||||
return; |
||||
} |
||||
|
||||
if (!(variable instanceof MultiValueVariable)) { |
||||
console.error('RepeatedRowBehavior: Variable is not a MultiValueVariable'); |
||||
return; |
||||
} |
||||
|
||||
if (!(this.parent instanceof SceneGridRow)) { |
||||
console.error('RepeatedRowBehavior: Parent is not a SceneGridRow'); |
||||
return; |
||||
} |
||||
|
||||
const layout = sceneGraph.getLayout(this); |
||||
|
||||
if (!(layout instanceof SceneGridLayout)) { |
||||
console.error('RepeatedRowBehavior: Layout is not a SceneGridLayout'); |
||||
return; |
||||
} |
||||
|
||||
const rowToRepeat = this.parent as SceneGridRow; |
||||
const { values, texts } = getMultiVariableValues(variable); |
||||
const rows: SceneGridRow[] = []; |
||||
const rowContentHeight = getRowContentHeight(this.state.sources); |
||||
let maxYOfRows = 0; |
||||
|
||||
// Loop through variable values and create repeates
|
||||
for (let index = 0; index < values.length; index++) { |
||||
const children: SceneGridItemLike[] = []; |
||||
|
||||
// Loop through panels inside row
|
||||
for (const source of this.state.sources) { |
||||
const sourceItemY = source.state.y ?? 0; |
||||
const itemY = sourceItemY + (rowContentHeight + 1) * index; |
||||
|
||||
const itemClone = source.clone({ |
||||
key: `${source.state.key}-clone-${index}`, |
||||
y: itemY, |
||||
}); |
||||
|
||||
//Make sure all the child scene objects have unique keys
|
||||
ensureUniqueKeys(itemClone, index); |
||||
|
||||
children.push(itemClone); |
||||
|
||||
if (maxYOfRows < itemY + itemClone.state.height!) { |
||||
maxYOfRows = itemY + itemClone.state.height!; |
||||
} |
||||
} |
||||
|
||||
const rowClone = this.getRowClone(rowToRepeat, index, values[index], texts[index], rowContentHeight, children); |
||||
rows.push(rowClone); |
||||
} |
||||
|
||||
updateLayout(layout, rows, maxYOfRows, rowToRepeat); |
||||
} |
||||
|
||||
getRowClone( |
||||
rowToRepeat: SceneGridRow, |
||||
index: number, |
||||
value: VariableValueSingle, |
||||
text: VariableValueSingle, |
||||
rowContentHeight: number, |
||||
children: SceneGridItemLike[] |
||||
): SceneGridRow { |
||||
if (index === 0) { |
||||
rowToRepeat.setState({ |
||||
// not activated
|
||||
$variables: new SceneVariableSet({ |
||||
variables: [new LocalValueVariable({ name: this.state.variableName, value, text: String(text) })], |
||||
}), |
||||
children, |
||||
}); |
||||
return rowToRepeat; |
||||
} |
||||
|
||||
const sourceRowY = rowToRepeat.state.y ?? 0; |
||||
|
||||
return rowToRepeat.clone({ |
||||
key: `${rowToRepeat.state.key}-clone-${index}`, |
||||
$variables: new SceneVariableSet({ |
||||
variables: [new LocalValueVariable({ name: this.state.variableName, value, text: String(text) })], |
||||
}), |
||||
$behaviors: [], |
||||
children, |
||||
y: sourceRowY + rowContentHeight * index + index, |
||||
}); |
||||
} |
||||
} |
||||
|
||||
function getRowContentHeight(panels: SceneGridItemLike[]): number { |
||||
let maxY = 0; |
||||
let minY = Number.MAX_VALUE; |
||||
|
||||
for (const panel of panels) { |
||||
if (panel.state.y! + panel.state.height! > maxY) { |
||||
maxY = panel.state.y! + panel.state.height!; |
||||
} |
||||
if (panel.state.y! < minY) { |
||||
minY = panel.state.y!; |
||||
} |
||||
} |
||||
|
||||
return maxY - minY; |
||||
} |
||||
|
||||
function updateLayout(layout: SceneGridLayout, rows: SceneGridRow[], maxYOfRows: number, rowToRepeat: SceneGridRow) { |
||||
const allChildren = getLayoutChildrenFilterOutRepeatClones(layout, rowToRepeat); |
||||
const index = allChildren.indexOf(rowToRepeat); |
||||
|
||||
if (index === -1) { |
||||
throw new Error('RowRepeaterBehavior: Parent row not found in layout children'); |
||||
} |
||||
|
||||
const newChildren = [...allChildren.slice(0, index), ...rows, ...allChildren.slice(index + 1)]; |
||||
|
||||
// Is there grid items after rows?
|
||||
if (allChildren.length > index + 1) { |
||||
const childrenAfter = allChildren.slice(index + 1); |
||||
const firstChildAfterY = childrenAfter[0].state.y!; |
||||
const diff = maxYOfRows - firstChildAfterY; |
||||
|
||||
for (const child of childrenAfter) { |
||||
if (child.state.y! < maxYOfRows) { |
||||
child.setState({ y: child.state.y! + diff }); |
||||
} |
||||
} |
||||
} |
||||
|
||||
layout.setState({ children: newChildren }); |
||||
} |
||||
|
||||
function getLayoutChildrenFilterOutRepeatClones(layout: SceneGridLayout, rowToRepeat: SceneGridRow) { |
||||
return layout.state.children.filter((child) => { |
||||
if (child.state.key?.startsWith(`${rowToRepeat.state.key}-clone-`)) { |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
}); |
||||
} |
||||
|
||||
function ensureUniqueKeys(item: SceneGridItemLike, rowIndex: number) { |
||||
item.forEachChild((child) => { |
||||
child.setState({ key: `${child.state.key}-row-${rowIndex}` }); |
||||
ensureUniqueKeys(child, rowIndex); |
||||
}); |
||||
} |
@ -0,0 +1,353 @@ |
||||
{ |
||||
"annotations": { |
||||
"list": [ |
||||
{ |
||||
"builtIn": 1, |
||||
"datasource": { |
||||
"type": "grafana", |
||||
"uid": "-- Grafana --" |
||||
}, |
||||
"enable": true, |
||||
"hide": true, |
||||
"iconColor": "rgba(0, 211, 255, 1)", |
||||
"name": "Annotations & Alerts", |
||||
"type": "dashboard" |
||||
} |
||||
] |
||||
}, |
||||
"editable": true, |
||||
"fiscalYearStartMonth": 0, |
||||
"graphTooltip": 0, |
||||
"links": [], |
||||
"liveNow": false, |
||||
"panels": [ |
||||
{ |
||||
"collapsed": false, |
||||
"gridPos": { |
||||
"h": 1, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 0 |
||||
}, |
||||
"id": 20, |
||||
"panels": [], |
||||
"title": "Row at the top - not repeated - saved expanded", |
||||
"type": "row" |
||||
}, |
||||
{ |
||||
"gridPos": { |
||||
"h": 2, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 1 |
||||
}, |
||||
"id": 15, |
||||
"options": { |
||||
"code": { |
||||
"language": "plaintext", |
||||
"showLineNumbers": false, |
||||
"showMiniMap": false |
||||
}, |
||||
"content": "<div class=\"center-vh\">\n Repeated row below. The row has \n a panel that is also repeated horizontally based\n on values in the $pod variable. \n</div>", |
||||
"mode": "markdown" |
||||
}, |
||||
"pluginVersion": "10.2.0-pre", |
||||
"type": "text" |
||||
}, |
||||
{ |
||||
"gridPos": { |
||||
"h": 1, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 3 |
||||
}, |
||||
"id": 16, |
||||
"panels": [], |
||||
"repeat": "server", |
||||
"repeatDirection": "h", |
||||
"title": "Row for server $server", |
||||
"type": "row" |
||||
}, |
||||
{ |
||||
"datasource": { |
||||
"type": "testdata", |
||||
"uid": "PD8C576611E62080A" |
||||
}, |
||||
"fieldConfig": { |
||||
"defaults": { |
||||
"color": { |
||||
"mode": "palette-classic" |
||||
}, |
||||
"custom": { |
||||
"axisCenteredZero": false, |
||||
"axisColorMode": "text", |
||||
"axisLabel": "", |
||||
"axisPlacement": "auto", |
||||
"axisShow": false, |
||||
"barAlignment": 0, |
||||
"drawStyle": "line", |
||||
"fillOpacity": 0, |
||||
"gradientMode": "none", |
||||
"hideFrom": { |
||||
"legend": false, |
||||
"tooltip": false, |
||||
"viz": false |
||||
}, |
||||
"insertNulls": false, |
||||
"lineInterpolation": "linear", |
||||
"lineWidth": 1, |
||||
"pointSize": 5, |
||||
"scaleDistribution": { |
||||
"type": "linear" |
||||
}, |
||||
"showPoints": "auto", |
||||
"spanNulls": false, |
||||
"stacking": { |
||||
"group": "A", |
||||
"mode": "none" |
||||
}, |
||||
"thresholdsStyle": { |
||||
"mode": "off" |
||||
} |
||||
}, |
||||
"mappings": [], |
||||
"thresholds": { |
||||
"mode": "absolute", |
||||
"steps": [ |
||||
{ |
||||
"color": "green", |
||||
"value": null |
||||
}, |
||||
{ |
||||
"color": "red", |
||||
"value": 80 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"overrides": [] |
||||
}, |
||||
"gridPos": { |
||||
"h": 10, |
||||
"w": 12, |
||||
"x": 0, |
||||
"y": 4 |
||||
}, |
||||
"id": 2, |
||||
"maxPerRow": 3, |
||||
"options": { |
||||
"legend": { |
||||
"calcs": [], |
||||
"displayMode": "list", |
||||
"placement": "bottom", |
||||
"showLegend": true |
||||
}, |
||||
"tooltip": { |
||||
"mode": "single", |
||||
"sort": "none" |
||||
} |
||||
}, |
||||
"repeat": "pod", |
||||
"repeatDirection": "h", |
||||
"targets": [ |
||||
{ |
||||
"alias": "server = $server, pod id = $pod ", |
||||
"datasource": { |
||||
"type": "testdata", |
||||
"uid": "PD8C576611E62080A" |
||||
}, |
||||
"refId": "A", |
||||
"scenarioId": "random_walk", |
||||
"seriesCount": 1 |
||||
} |
||||
], |
||||
"title": "server = $server, pod = $pod", |
||||
"type": "timeseries" |
||||
}, |
||||
{ |
||||
"collapsed": true, |
||||
"gridPos": { |
||||
"h": 1, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 25 |
||||
}, |
||||
"id": 25, |
||||
"panels": [ |
||||
{ |
||||
"gridPos": { |
||||
"h": 2, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 26 |
||||
}, |
||||
"id": 30, |
||||
"options": { |
||||
"code": { |
||||
"language": "plaintext", |
||||
"showLineNumbers": false, |
||||
"showMiniMap": false |
||||
}, |
||||
"content": "<div class=\"center-vh\">\n Just a panel\n</div>", |
||||
"mode": "markdown" |
||||
}, |
||||
"pluginVersion": "10.2.0-pre", |
||||
"type": "text" |
||||
} |
||||
], |
||||
"title": "Row at the bottom - not repeated - saved collapsed ", |
||||
"type": "row" |
||||
} |
||||
], |
||||
"refresh": "", |
||||
"schemaVersion": 38, |
||||
"tags": ["templating", "gdev"], |
||||
"templating": { |
||||
"list": [ |
||||
{ |
||||
"current": { |
||||
"selected": true, |
||||
"text": ["A", "B"], |
||||
"value": ["A", "B"] |
||||
}, |
||||
"hide": 0, |
||||
"includeAll": true, |
||||
"multi": true, |
||||
"name": "server", |
||||
"options": [ |
||||
{ |
||||
"selected": false, |
||||
"text": "All", |
||||
"value": "$__all" |
||||
}, |
||||
{ |
||||
"selected": true, |
||||
"text": "A", |
||||
"value": "A" |
||||
}, |
||||
{ |
||||
"selected": true, |
||||
"text": "B", |
||||
"value": "B" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "C", |
||||
"value": "C" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "D", |
||||
"value": "D" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "E", |
||||
"value": "E" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "F", |
||||
"value": "F" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "E", |
||||
"value": "E" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "G", |
||||
"value": "G" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "H", |
||||
"value": "H" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "I", |
||||
"value": "I" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "J", |
||||
"value": "J" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "K", |
||||
"value": "K" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "L", |
||||
"value": "L" |
||||
} |
||||
], |
||||
"query": "A,B,C,D,E,F,E,G,H,I,J,K,L", |
||||
"queryValue": "", |
||||
"skipUrlSync": false, |
||||
"type": "custom" |
||||
}, |
||||
{ |
||||
"current": { |
||||
"selected": true, |
||||
"text": ["Bob", "Rob"], |
||||
"value": ["1", "2"] |
||||
}, |
||||
"hide": 0, |
||||
"includeAll": true, |
||||
"multi": true, |
||||
"name": "pod", |
||||
"options": [ |
||||
{ |
||||
"selected": false, |
||||
"text": "All", |
||||
"value": "$__all" |
||||
}, |
||||
{ |
||||
"selected": true, |
||||
"text": "Bob", |
||||
"value": "1" |
||||
}, |
||||
{ |
||||
"selected": true, |
||||
"text": "Rob", |
||||
"value": "2" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "Sod", |
||||
"value": "3" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "Hod", |
||||
"value": "4" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "Cod", |
||||
"value": "5" |
||||
} |
||||
], |
||||
"query": "Bob : 1, Rob : 2,Sod : 3, Hod : 4, Cod : 5", |
||||
"queryValue": "", |
||||
"skipUrlSync": false, |
||||
"type": "custom" |
||||
} |
||||
] |
||||
}, |
||||
"time": { |
||||
"from": "now-6h", |
||||
"to": "now" |
||||
}, |
||||
"timepicker": {}, |
||||
"timezone": "", |
||||
"title": "Repeating rows", |
||||
"uid": "Repeating-rows-uid", |
||||
"version": 1, |
||||
"weekStart": "" |
||||
} |
Loading…
Reference in new issue