|
|
|
@ -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; |
|
|
|
|
} |
|
|
|
|