Merge pull request #12991 from pgiraud/repeat_min_span

Repeating panels - Max per row
This commit is contained in:
Torkel Ödegaard 2019-01-11 09:31:21 +01:00 committed by GitHub
commit fda0f92cf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 65 additions and 21 deletions

View File

@ -51,7 +51,7 @@ When a user creates a new dashboard, a new dashboard JSON object is initialized
"list": []
},
"refresh": "5s",
"schemaVersion": 16,
"schemaVersion": 17,
"version": 0,
"links": []
}

View File

@ -292,9 +292,11 @@ The `direction` controls how the panels will be arranged.
By choosing `horizontal` the panels will be arranged side-by-side. Grafana will automatically adjust the width
of each repeated panel so that the whole row is filled. Currently, you cannot mix other panels on a row with a repeated
panel. Each panel will never be smaller that the provided `Min width` if you have many selected values.
panel.
By choosing `vertical` the panels will be arranged from top to bottom in a column. The `Min width` doesn't have any effect in this case. The width of the repeated panels will be the same as of the first panel (the original template) being repeated.
Set `Max per row` to tell grafana how many panels per row you want at most. It defaults to *4* if you don't set anything.
By choosing `vertical` the panels will be arranged from top to bottom in a column. The width of the repeated panels will be the same as of the first panel (the original template) being repeated.
Only make changes to the first panel (the original template). To have the changes take effect on all panels you need to trigger a dynamic dashboard re-build.
You can do this by either changing the variable value (that is the basis for the repeat) or reload the dashboard.

View File

@ -112,7 +112,7 @@ func NewDashboard(title string) *Dashboard {
func NewDashboardFolder(title string) *Dashboard {
folder := NewDashboard(title)
folder.IsFolder = true
folder.Data.Set("schemaVersion", 16)
folder.Data.Set("schemaVersion", 17)
folder.Data.Set("version", 0)
folder.IsFolder = true
return folder

View File

@ -0,0 +1,8 @@
import getFactors from 'app/core/utils/factors';
describe('factors', () => {
it('should return factors for 12', () => {
const factors = getFactors(12);
expect(factors).toEqual([1, 2, 3, 4, 6, 12]);
});
});

View File

@ -0,0 +1,5 @@
// Returns the factors of a number
// Example getFactors(12) -> [1, 2, 3, 4, 6, 12]
export default function getFactors(num: number): number[] {
return Array.from(new Array(num + 1), (_, i) => i).filter(i => num % i === 0);
}

View File

@ -9,6 +9,7 @@ import {
} from 'app/core/constants';
import { PanelModel } from './panel_model';
import { DashboardModel } from './dashboard_model';
import getFactors from 'app/core/utils/factors';
export class DashboardMigrator {
dashboard: DashboardModel;
@ -21,7 +22,7 @@ export class DashboardMigrator {
let i, j, k, n;
const oldVersion = this.dashboard.schemaVersion;
const panelUpgrades = [];
this.dashboard.schemaVersion = 16;
this.dashboard.schemaVersion = 17;
if (oldVersion === this.dashboard.schemaVersion) {
return;
@ -368,6 +369,24 @@ export class DashboardMigrator {
this.upgradeToGridLayout(old);
}
if (oldVersion < 17) {
panelUpgrades.push(panel => {
if (panel.minSpan) {
const max = GRID_COLUMN_COUNT / panel.minSpan;
const factors = getFactors(GRID_COLUMN_COUNT);
// find the best match compared to factors
// (ie. [1,2,3,4,6,12,24] for 24 columns)
panel.maxPerRow =
factors[
_.findIndex(factors, o => {
return o > max;
}) - 1
];
}
delete panel.minSpan;
});
}
if (panelUpgrades.length === 0) {
return;
}

View File

@ -442,7 +442,7 @@ export class DashboardModel {
}
const selectedOptions = this.getSelectedVariableOptions(variable);
const minWidth = panel.minSpan || 6;
const maxPerRow = panel.maxPerRow || 4;
let xPos = 0;
let yPos = panel.gridPos.y;
@ -462,7 +462,7 @@ export class DashboardModel {
} else {
// set width based on how many are selected
// assumed the repeated panels should take up full row width
copy.gridPos.w = Math.max(GRID_COLUMN_COUNT / selectedOptions.length, minWidth);
copy.gridPos.w = Math.max(GRID_COLUMN_COUNT / selectedOptions.length, GRID_COLUMN_COUNT / maxPerRow);
copy.gridPos.x = xPos;
copy.gridPos.y = yPos;

View File

@ -77,7 +77,7 @@ export class PanelModel {
repeatPanelId?: number;
repeatDirection?: string;
repeatedByRow?: boolean;
minSpan?: number;
maxPerRow?: number;
collapsed?: boolean;
panels?: any;
soloMode?: boolean;

View File

@ -127,7 +127,7 @@ describe('DashboardModel', () => {
});
it('dashboard schema version should be set to latest', () => {
expect(model.schemaVersion).toBe(16);
expect(model.schemaVersion).toBe(17);
});
it('graph thresholds should be migrated', () => {
@ -364,14 +364,6 @@ describe('DashboardModel', () => {
expect(dashboard.panels.length).toBe(2);
});
it('minSpan should be twice', () => {
model.rows = [createRow({ height: 8 }, [[6]])];
model.rows[0].panels[0] = { minSpan: 12 };
const dashboard = new DashboardModel(model);
expect(dashboard.panels[0].minSpan).toBe(24);
});
it('should assign id', () => {
model.rows = [createRow({ collapse: true, height: 8 }, [[6], [6]])];
model.rows[0].panels[0] = {};
@ -380,6 +372,16 @@ describe('DashboardModel', () => {
expect(dashboard.panels[0].id).toBe(1);
});
});
describe('when migrating from minSpan to maxPerRow', () => {
it('maxPerRow should be correct', () => {
const model = {
panels: [{ minSpan: 8 }],
};
const dashboard = new DashboardModel(model);
expect(dashboard.panels[0].maxPerRow).toBe(3);
});
});
});
function createRow(options, panelDescriptions: any[]) {

View File

@ -5,6 +5,7 @@ import Remarkable from 'remarkable';
import config from 'app/core/config';
import { profiler } from 'app/core/core';
import { Emitter } from 'app/core/core';
import getFactors from 'app/core/utils/factors';
import {
duplicatePanel,
copyPanel as copyPanelUtil,
@ -12,7 +13,7 @@ import {
sharePanel as sharePanelUtil,
} from 'app/features/dashboard/utils/panel';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants';
export class PanelCtrl {
panel: any;
@ -32,6 +33,7 @@ export class PanelCtrl {
events: Emitter;
timing: any;
loading: boolean;
maxPanelsPerRowOptions: number[];
constructor($scope, $injector) {
this.$injector = $injector;
@ -92,6 +94,7 @@ export class PanelCtrl {
if (!this.editModeInitiated) {
this.editModeInitiated = true;
this.events.emit('init-edit-mode', null);
this.maxPanelsPerRowOptions = getFactors(GRID_COLUMN_COUNT);
}
}

View File

@ -32,12 +32,17 @@
</select>
</div>
<div class="gf-form" ng-show="ctrl.panel.repeat && ctrl.panel.repeatDirection == 'h'">
<span class="gf-form-label width-9">Min width</span>
<select class="gf-form-input" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]">
<span class="gf-form-label width-9">Max per row</span>
<select class="gf-form-input" ng-model="ctrl.panel.maxPerRow" ng-options="f for f in [2,3,4,6,12,24]">
<option value=""></option>
</select>
</div>
<div class="gf-form-hint">
<div class="gf-form-hint-text muted">
Note: You may need to change the variable selection to see this in action.
</div>
</div>
</div>
</div>
</div>

View File

@ -65,7 +65,7 @@
}
],
"rows": [],
"schemaVersion": 16,
"schemaVersion": 17,
"style": "dark",
"tags": [],
"templating": {