mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Fix repeated row panel placement with larger number of rows (#72011)
Closes #69833
This commit is contained in:
@@ -2333,17 +2333,16 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "23"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "24"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "25"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "26"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "26"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "27"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "28"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "29"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "28"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "29"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "30"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "31"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "31"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "32"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "33"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "34"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "35"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "36"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "35"]
|
||||
],
|
||||
"public/app/features/dashboard/state/PanelModel.test.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
PanelModel as IPanelModel,
|
||||
TimeRange,
|
||||
TimeZone,
|
||||
TypedVariableModel,
|
||||
UrlQueryValue,
|
||||
} from '@grafana/data';
|
||||
import { RefreshEvent, TimeRangeUpdatedEvent, config } from '@grafana/runtime';
|
||||
@@ -42,7 +43,7 @@ import { getTimeSrv } from '../services/TimeSrv';
|
||||
import { mergePanels, PanelMergeInfo } from '../utils/panelMerge';
|
||||
|
||||
import { DashboardMigrator } from './DashboardMigrator';
|
||||
import { GridPos, PanelModel, autoMigrateAngular } from './PanelModel';
|
||||
import { PanelModel, autoMigrateAngular } from './PanelModel';
|
||||
import { TimeModel } from './TimeModel';
|
||||
import { deleteScopeVars, isOnTheSameGridRow } from './utils';
|
||||
|
||||
@@ -667,12 +668,13 @@ export class DashboardModel implements TimeModel {
|
||||
}
|
||||
|
||||
getRowRepeatClone(sourceRowPanel: PanelModel, valueIndex: number, sourcePanelIndex: number) {
|
||||
// if first clone return source
|
||||
// if first clone, return source
|
||||
if (valueIndex === 0) {
|
||||
if (!sourceRowPanel.collapsed) {
|
||||
const rowPanels = this.getRowPanels(sourcePanelIndex);
|
||||
sourceRowPanel.panels = rowPanels;
|
||||
}
|
||||
|
||||
return sourceRowPanel;
|
||||
}
|
||||
|
||||
@@ -682,11 +684,13 @@ export class DashboardModel implements TimeModel {
|
||||
if (sourceRowPanel.collapsed) {
|
||||
rowPanels = cloneDeep(sourceRowPanel.panels) ?? [];
|
||||
clone.panels = rowPanels;
|
||||
|
||||
// insert copied row after preceding row
|
||||
insertPos = sourcePanelIndex + valueIndex;
|
||||
} else {
|
||||
rowPanels = this.getRowPanels(sourcePanelIndex);
|
||||
clone.panels = rowPanels.map((panel) => panel.getSaveModel());
|
||||
|
||||
// insert copied row after preceding row's panels
|
||||
insertPos = sourcePanelIndex + (rowPanels.length + 1) * valueIndex;
|
||||
}
|
||||
@@ -757,59 +761,56 @@ export class DashboardModel implements TimeModel {
|
||||
}
|
||||
}
|
||||
|
||||
repeatRow(panel: PanelModel, panelIndex: number, variable: any) {
|
||||
repeatRow(panel: PanelModel, panelIndex: number, variable: TypedVariableModel) {
|
||||
const selectedOptions = this.getSelectedVariableOptions(variable);
|
||||
let yPos = panel.gridPos.y;
|
||||
|
||||
function setScopedVars(panel: PanelModel, variableOption: any) {
|
||||
panel.scopedVars ??= {};
|
||||
panel.scopedVars[variable.name] = variableOption;
|
||||
}
|
||||
|
||||
for (let optionIndex = 0; optionIndex < selectedOptions.length; optionIndex++) {
|
||||
const option = selectedOptions[optionIndex];
|
||||
const rowCopy = this.getRowRepeatClone(panel, optionIndex, panelIndex);
|
||||
const curOption = selectedOptions[optionIndex];
|
||||
const rowClone = this.getRowRepeatClone(panel, optionIndex, panelIndex);
|
||||
|
||||
setScopedVars(rowCopy, option);
|
||||
setScopedVars(rowClone, variable, curOption);
|
||||
|
||||
const rowHeight = this.getRowHeight(rowCopy);
|
||||
const rowPanels = rowCopy.panels || [];
|
||||
const rowHeight = this.getRowHeight(rowClone);
|
||||
const panelsInRow = rowClone.panels || [];
|
||||
let panelBelowIndex;
|
||||
|
||||
if (panel.collapsed) {
|
||||
// For collapsed row just copy its panels and set scoped vars and proper IDs
|
||||
for (const rowPanel of rowPanels) {
|
||||
setScopedVars(rowPanel, option);
|
||||
// For a collapsed row, just copy its panels, set scoped vars and proper IDs
|
||||
for (const panelInRow of panelsInRow) {
|
||||
setScopedVars(panelInRow, variable, curOption);
|
||||
if (optionIndex > 0) {
|
||||
this.updateRepeatedPanelIds(rowPanel, true);
|
||||
this.updateRepeatedPanelIds(panelInRow, true);
|
||||
}
|
||||
}
|
||||
rowCopy.gridPos.y += optionIndex;
|
||||
yPos += optionIndex;
|
||||
|
||||
// push nth row clone's y-pos down by n
|
||||
rowClone.gridPos.y += optionIndex;
|
||||
panelBelowIndex = panelIndex + optionIndex + 1;
|
||||
} else {
|
||||
// insert after 'row' panel
|
||||
const insertPos = panelIndex + (rowPanels.length + 1) * optionIndex + 1;
|
||||
rowPanels.forEach((rowPanel: PanelModel, i: number) => {
|
||||
setScopedVars(rowPanel, option);
|
||||
// insert after row panel
|
||||
const insertPos = panelIndex + (panelsInRow.length + 1) * optionIndex + 1;
|
||||
panelsInRow.forEach((panelInRow, i) => {
|
||||
setScopedVars(panelInRow, variable, curOption);
|
||||
|
||||
if (optionIndex > 0) {
|
||||
const cloneRowPanel = new PanelModel(rowPanel);
|
||||
this.updateRepeatedPanelIds(cloneRowPanel, true);
|
||||
// For exposed row additionally set proper Y grid position and add it to dashboard panels
|
||||
cloneRowPanel.gridPos.y += rowHeight * optionIndex;
|
||||
this.panels.splice(insertPos + i, 0, cloneRowPanel);
|
||||
const panelInRowClone = new PanelModel(panelInRow);
|
||||
this.updateRepeatedPanelIds(panelInRowClone, true);
|
||||
|
||||
// For exposed row, set correct grid y-position and add it to dashboard panels
|
||||
panelInRowClone.gridPos.y += rowHeight * optionIndex;
|
||||
this.panels.splice(insertPos + i, 0, panelInRowClone);
|
||||
}
|
||||
});
|
||||
rowCopy.panels = [];
|
||||
rowCopy.gridPos.y += rowHeight * optionIndex;
|
||||
yPos += rowHeight;
|
||||
panelBelowIndex = insertPos + rowPanels.length;
|
||||
|
||||
rowClone.panels = [];
|
||||
rowClone.gridPos.y += rowHeight * optionIndex;
|
||||
panelBelowIndex = insertPos + panelsInRow.length;
|
||||
}
|
||||
|
||||
// Update gridPos for panels below if we inserted more than 1 repeated row panel
|
||||
if (selectedOptions.length > 1) {
|
||||
for (const panel of this.panels.slice(panelBelowIndex)) {
|
||||
panel.gridPos.y += yPos;
|
||||
panel.gridPos.y += rowHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -841,12 +842,13 @@ export class DashboardModel implements TimeModel {
|
||||
getRowHeight(rowPanel: PanelModel): number {
|
||||
if (!rowPanel.panels || rowPanel.panels.length === 0) {
|
||||
return 0;
|
||||
} else if (rowPanel.collapsed) {
|
||||
// A collapsed row will always have height 1
|
||||
return 1;
|
||||
}
|
||||
|
||||
const rowYPos = rowPanel.gridPos.y;
|
||||
const positions = map(rowPanel.panels, 'gridPos');
|
||||
const maxPos = maxBy(positions, (pos: GridPos) => pos.y + pos.h);
|
||||
return maxPos!.y + maxPos!.h - rowYPos;
|
||||
const maxYPos = maxBy(rowPanel.panels, ({ gridPos }) => gridPos.y + gridPos.h)!.gridPos;
|
||||
return maxYPos.y + maxYPos.h - rowPanel.gridPos.y;
|
||||
}
|
||||
|
||||
removePanel(panel: PanelModel) {
|
||||
@@ -1300,3 +1302,8 @@ export class DashboardModel implements TimeModel {
|
||||
function isPanelWithLegend(panel: PanelModel): panel is PanelModel & Pick<Required<PanelModel>, 'legend'> {
|
||||
return Boolean(panel.legend);
|
||||
}
|
||||
|
||||
function setScopedVars(panel: PanelModel, variable: TypedVariableModel, variableOption: any) {
|
||||
panel.scopedVars ??= {};
|
||||
panel.scopedVars[variable.name] = variableOption;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user