newgrid: added constants, changed grid to 24 cols, added tests for panel repeats

pull/9559/head
Torkel Ödegaard 8 years ago
parent 9fb4f61975
commit e5a6cb6241
  1. 6
      public/app/core/constants.ts
  2. 617
      public/app/features/dashboard/dashboard_model.ts
  3. 9
      public/app/features/dashboard/dashgrid/DashboardGrid.tsx
  4. 17
      public/app/features/dashboard/repeat_option/repeat_option.ts
  5. 114
      public/app/features/dashboard/specs/dashboard_model_specs.ts
  6. 287
      public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts
  7. 4
      public/app/features/panel/panel_ctrl.ts
  8. 16
      public/app/partials/panelgeneral.html
  9. 10
      public/dashboards/home.json

@ -0,0 +1,6 @@
export const GRID_CELL_HEIGHT = 20;
export const GRID_CELL_VMARGIN = 10;
export const GRID_COLUMN_COUNT = 24;

@ -1,15 +1,13 @@
import moment from 'moment'; import moment from 'moment';
import _ from 'lodash'; import _ from 'lodash';
import {GRID_COLUMN_COUNT, GRID_CELL_HEIGHT} from 'app/core/constants';
import {DEFAULT_ANNOTATION_COLOR} from 'app/core/utils/colors'; import {DEFAULT_ANNOTATION_COLOR} from 'app/core/utils/colors';
import {Emitter, contextSrv} from 'app/core/core'; import {Emitter, contextSrv} from 'app/core/core';
import {DashboardRow} from './row/row_model';
import {PanelModel} from './panel_model';
import sortByKeys from 'app/core/utils/sort_by_keys'; import sortByKeys from 'app/core/utils/sort_by_keys';
export const CELL_HEIGHT = 30; import {DashboardRow} from './row/row_model';
export const CELL_VMARGIN = 10; import {PanelModel} from './panel_model';
export const COL_COUNT = 12;
export class DashboardModel { export class DashboardModel {
id: any; id: any;
@ -48,10 +46,10 @@ export class DashboardModel {
events: Emitter; events: Emitter;
static nonPersistedProperties: {[str: string]: boolean} = { static nonPersistedProperties: {[str: string]: boolean} = {
"events": true, events: true,
"meta": true, meta: true,
"panels": true, // needs special handling panels: true, // needs special handling
"templating": true, // needs special handling templating: true, // needs special handling
}; };
constructor(data, meta?) { constructor(data, meta?) {
@ -66,12 +64,12 @@ export class DashboardModel {
this.autoUpdate = data.autoUpdate; this.autoUpdate = data.autoUpdate;
this.description = data.description; this.description = data.description;
this.tags = data.tags || []; this.tags = data.tags || [];
this.style = data.style || "dark"; this.style = data.style || 'dark';
this.timezone = data.timezone || ''; this.timezone = data.timezone || '';
this.editable = data.editable !== false; this.editable = data.editable !== false;
this.graphTooltip = data.graphTooltip || 0; this.graphTooltip = data.graphTooltip || 0;
this.hideControls = data.hideControls || false; this.hideControls = data.hideControls || false;
this.time = data.time || { from: 'now-6h', to: 'now' }; this.time = data.time || {from: 'now-6h', to: 'now'};
this.timepicker = data.timepicker || {}; this.timepicker = data.timepicker || {};
this.templating = this.ensureListExist(data.templating); this.templating = this.ensureListExist(data.templating);
this.annotations = this.ensureListExist(data.annotations); this.annotations = this.ensureListExist(data.annotations);
@ -144,7 +142,7 @@ export class DashboardModel {
// get variable save models // get variable save models
copy.templating = { copy.templating = {
list: _.map(this.templating.list, variable => variable.getSaveModel ? variable.getSaveModel() : variable), list: _.map(this.templating.list, variable => (variable.getSaveModel ? variable.getSaveModel() : variable)),
}; };
// get panel save models // get panel save models
@ -166,17 +164,24 @@ export class DashboardModel {
} }
private ensureListExist(data) { private ensureListExist(data) {
if (!data) { data = {}; } if (!data) {
if (!data.list) { data.list = []; } data = {};
}
if (!data.list) {
data.list = [];
}
return data; return data;
} }
getNextPanelId() { getNextPanelId() {
var j, panel, max = 0; let max = 0;
for (j = 0; j < this.panels.length; j++) {
panel = this.panels[j]; for (let panel of this.panels) {
if (panel.id > max) { max = panel.id; } if (panel.id > max) {
max = panel.id;
}
} }
return max + 1; return max + 1;
} }
@ -225,9 +230,13 @@ export class DashboardModel {
} }
this.iteration = (this.iteration || new Date().getTime()) + 1; this.iteration = (this.iteration || new Date().getTime()) + 1;
let panelsToRemove = []; let panelsToRemove = [];
// cleanup scopedVars
for (let panel of this.panels) {
delete panel.scopedVars;
}
for (let panel of this.panels) { for (let panel of this.panels) {
if (panel.repeat) { if (panel.repeat) {
if (!cleanUpOnly) { if (!cleanUpOnly) {
@ -263,7 +272,9 @@ export class DashboardModel {
repeatPanel(panel: PanelModel) { repeatPanel(panel: PanelModel) {
var variable = _.find(this.templating.list, {name: panel.repeat}); var variable = _.find(this.templating.list, {name: panel.repeat});
if (!variable) { return; } if (!variable) {
return;
}
var selected; var selected;
if (variable.current.text === 'All') { if (variable.current.text === 'All') {
@ -276,7 +287,7 @@ export class DashboardModel {
var option = selected[index]; var option = selected[index];
var copy = this.getRepeatClone(panel, index); var copy = this.getRepeatClone(panel, index);
copy.scopedVars = copy.scopedVars || {}; copy.scopedVars = {};
copy.scopedVars[variable.name] = option; copy.scopedVars[variable.name] = option;
// souce panel uses original possition // souce panel uses original possition
@ -285,9 +296,9 @@ export class DashboardModel {
} }
if (panel.repeatDirection === 'Y') { if (panel.repeatDirection === 'Y') {
copy.gridPos.y = panel.gridPos.y + (panel.gridPos.h*index); copy.gridPos.y = panel.gridPos.y + panel.gridPos.h * index;
} else { } else {
copy.gridPos.x = panel.gridPos.x + (panel.gridPos.w*index); copy.gridPos.x = panel.gridPos.x + panel.gridPos.w * index;
} }
} }
} }
@ -304,46 +315,39 @@ export class DashboardModel {
updateSubmenuVisibility() { updateSubmenuVisibility() {
this.meta.submenuEnabled = (() => { this.meta.submenuEnabled = (() => {
if (this.links.length > 0) { return true; } if (this.links.length > 0) {
return true;
}
var visibleVars = _.filter(this.templating.list, variable => variable.hide !== 2); var visibleVars = _.filter(this.templating.list, variable => variable.hide !== 2);
if (visibleVars.length > 0) { return true; } if (visibleVars.length > 0) {
return true;
}
var visibleAnnotations = _.filter(this.annotations.list, annotation => annotation.hide !== true); var visibleAnnotations = _.filter(this.annotations.list, annotation => annotation.hide !== true);
if (visibleAnnotations.length > 0) { return true; } if (visibleAnnotations.length > 0) {
return true;
}
return false; return false;
})(); })();
} }
getPanelInfoById(panelId) { getPanelInfoById(panelId) {
var result: any = {}; for (let i = 0; i < this.panels.length; i++) {
_.each(this.rows, function(row) { if (this.panels[i].id === panelId) {
_.each(row.panels, function(panel, index) { return {
if (panel.id === panelId) { panel: this.panels[i],
result.panel = panel; index: i,
result.row = row; };
result.index = index;
}
});
});
_.each(this.panels, function(panel, index) {
if (panel.id === panelId) {
result.panel = panel;
result.index = index;
} }
});
if (!result.panel) {
return null;
} }
return result; return null;
} }
duplicatePanel(panel) { duplicatePanel(panel) {
const newPanel = _.cloneDeep(panel.getSaveModel()); const newPanel = panel.getSaveModel();
newPanel.id = this.getNextPanelId(); newPanel.id = this.getNextPanelId();
delete newPanel.repeat; delete newPanel.repeat;
@ -356,7 +360,7 @@ export class DashboardModel {
delete newPanel.alert; delete newPanel.alert;
// does it fit to the right? // does it fit to the right?
if (panel.gridPos.x + (panel.gridPos.w*2) <= COL_COUNT) { if (panel.gridPos.x + panel.gridPos.w * 2 <= GRID_COLUMN_COUNT) {
newPanel.gridPos.x += panel.gridPos.w; newPanel.gridPos.x += panel.gridPos.w;
} else { } else {
// add bellow // add bellow
@ -372,9 +376,7 @@ export class DashboardModel {
format = format || 'YYYY-MM-DD HH:mm:ss'; format = format || 'YYYY-MM-DD HH:mm:ss';
let timezone = this.getTimezone(); let timezone = this.getTimezone();
return timezone === 'browser' ? return timezone === 'browser' ? moment(date).format(format) : moment.utc(date).format(format);
moment(date).format(format) :
moment.utc(date).format(format);
} }
destroy() { destroy() {
@ -407,9 +409,7 @@ export class DashboardModel {
getRelativeTime(date) { getRelativeTime(date) {
date = moment.isMoment(date) ? date : moment(date); date = moment.isMoment(date) ? date : moment(date);
return this.timezone === 'browser' ? return this.timezone === 'browser' ? moment(date).fromNow() : moment.utc(date).fromNow();
moment(date).fromNow() :
moment.utc(date).fromNow();
} }
getNextQueryLetter(panel) { getNextQueryLetter(panel) {
@ -442,7 +442,6 @@ export class DashboardModel {
// version 2 schema changes // version 2 schema changes
if (oldVersion < 2) { if (oldVersion < 2) {
if (old.services) { if (old.services) {
if (old.services.filter) { if (old.services.filter) {
this.time = old.services.filter.time; this.time = old.services.filter.time;
@ -460,7 +459,9 @@ export class DashboardModel {
return; return;
} }
if (_.isBoolean(panel.legend)) { panel.legend = { show: panel.legend }; } if (_.isBoolean(panel.legend)) {
panel.legend = {show: panel.legend};
}
if (panel.grid) { if (panel.grid) {
if (panel.grid.min) { if (panel.grid.min) {
@ -502,9 +503,11 @@ export class DashboardModel {
if (oldVersion < 4) { if (oldVersion < 4) {
// move aliasYAxis changes // move aliasYAxis changes
panelUpgrades.push(function(panel) { panelUpgrades.push(function(panel) {
if (panel.type !== 'graph') { return; } if (panel.type !== 'graph') {
return;
}
_.each(panel.aliasYAxis, function(value, key) { _.each(panel.aliasYAxis, function(value, key) {
panel.seriesOverrides = [{ alias: key, yaxis: value }]; panel.seriesOverrides = [{alias: key, yaxis: value}];
}); });
delete panel.aliasYAxis; delete panel.aliasYAxis;
}); });
@ -512,7 +515,7 @@ export class DashboardModel {
if (oldVersion < 6) { if (oldVersion < 6) {
// move pulldowns to new schema // move pulldowns to new schema
var annotations = _.find(old.pulldowns, { type: 'annotations' }); var annotations = _.find(old.pulldowns, {type: 'annotations'});
if (annotations) { if (annotations) {
this.annotations = { this.annotations = {
@ -521,12 +524,20 @@ export class DashboardModel {
} }
// update template variables // update template variables
for (i = 0 ; i < this.templating.list.length; i++) { for (i = 0; i < this.templating.list.length; i++) {
var variable = this.templating.list[i]; var variable = this.templating.list[i];
if (variable.datasource === void 0) { variable.datasource = null; } if (variable.datasource === void 0) {
if (variable.type === 'filter') { variable.type = 'query'; } variable.datasource = null;
if (variable.type === void 0) { variable.type = 'query'; } }
if (variable.allFormat === void 0) { variable.allFormat = 'glob'; } if (variable.type === 'filter') {
variable.type = 'query';
}
if (variable.type === void 0) {
variable.type = 'query';
}
if (variable.allFormat === void 0) {
variable.allFormat = 'glob';
}
} }
} }
@ -537,277 +548,299 @@ export class DashboardModel {
// ensure query refIds // ensure query refIds
panelUpgrades.push(function(panel) { panelUpgrades.push(function(panel) {
_.each(panel.targets, function(target) { _.each(
if (!target.refId) { panel.targets,
target.refId = this.getNextQueryLetter(panel); function(target) {
if (!target.refId) {
target.refId = this.getNextQueryLetter(panel);
} }
}.bind(this)); }.bind(this),
}); );
} });
}
if (oldVersion < 8) { if (oldVersion < 8) {
panelUpgrades.push(function(panel) { panelUpgrades.push(function(panel) {
_.each(panel.targets, function(target) { _.each(panel.targets, function(target) {
// update old influxdb query schema // update old influxdb query schema
if (target.fields && target.tags && target.groupBy) { if (target.fields && target.tags && target.groupBy) {
if (target.rawQuery) { if (target.rawQuery) {
delete target.fields; delete target.fields;
delete target.fill; delete target.fill;
} else { } else {
target.select = _.map(target.fields, function(field) { target.select = _.map(target.fields, function(field) {
var parts = []; var parts = [];
parts.push({type: 'field', params: [field.name]}); parts.push({type: 'field', params: [field.name]});
parts.push({type: field.func, params: []}); parts.push({type: field.func, params: []});
if (field.mathExpr) { if (field.mathExpr) {
parts.push({type: 'math', params: [field.mathExpr]}); parts.push({type: 'math', params: [field.mathExpr]});
}
if (field.asExpr) {
parts.push({type: 'alias', params: [field.asExpr]});
}
return parts;
});
delete target.fields;
_.each(target.groupBy, function(part) {
if (part.type === 'time' && part.interval) {
part.params = [part.interval];
delete part.interval;
}
if (part.type === 'tag' && part.key) {
part.params = [part.key];
delete part.key;
}
});
if (target.fill) {
target.groupBy.push({type: 'fill', params: [target.fill]});
delete target.fill;
} }
if (field.asExpr) {
parts.push({type: 'alias', params: [field.asExpr]});
}
return parts;
});
delete target.fields;
_.each(target.groupBy, function(part) {
if (part.type === 'time' && part.interval) {
part.params = [part.interval];
delete part.interval;
}
if (part.type === 'tag' && part.key) {
part.params = [part.key];
delete part.key;
}
});
if (target.fill) {
target.groupBy.push({type: 'fill', params: [target.fill]});
delete target.fill;
} }
} }
}); }
}); });
} });
}
// schema version 9 changes // schema version 9 changes
if (oldVersion < 9) { if (oldVersion < 9) {
// move aliasYAxis changes // move aliasYAxis changes
panelUpgrades.push(function(panel) { panelUpgrades.push(function(panel) {
if (panel.type !== 'singlestat' && panel.thresholds !== "") { return; } if (panel.type !== 'singlestat' && panel.thresholds !== '') {
return;
}
if (panel.thresholds) { if (panel.thresholds) {
var k = panel.thresholds.split(","); var k = panel.thresholds.split(',');
if (k.length >= 3) { if (k.length >= 3) {
k.shift(); k.shift();
panel.thresholds = k.join(","); panel.thresholds = k.join(',');
}
} }
}); }
} });
}
// schema version 10 changes // schema version 10 changes
if (oldVersion < 10) { if (oldVersion < 10) {
// move aliasYAxis changes // move aliasYAxis changes
panelUpgrades.push(function(panel) { panelUpgrades.push(function(panel) {
if (panel.type !== 'table') { return; } if (panel.type !== 'table') {
return;
_.each(panel.styles, function(style) { }
if (style.thresholds && style.thresholds.length >= 3) {
var k = style.thresholds;
k.shift();
style.thresholds = k;
}
});
});
}
if (oldVersion < 12) { _.each(panel.styles, function(style) {
// update template variables if (style.thresholds && style.thresholds.length >= 3) {
_.each(this.templating.list, function(templateVariable) { var k = style.thresholds;
if (templateVariable.refresh) { templateVariable.refresh = 1; } k.shift();
if (!templateVariable.refresh) { templateVariable.refresh = 0; } style.thresholds = k;
if (templateVariable.hideVariable) {
templateVariable.hide = 2;
} else if (templateVariable.hideLabel) {
templateVariable.hide = 1;
} }
}); });
} });
}
if (oldVersion < 12) { if (oldVersion < 12) {
// update graph yaxes changes // update template variables
panelUpgrades.push(function(panel) { _.each(this.templating.list, function(templateVariable) {
if (panel.type !== 'graph') { return; } if (templateVariable.refresh) {
if (!panel.grid) { return; } templateVariable.refresh = 1;
}
if (!panel.yaxes) { if (!templateVariable.refresh) {
panel.yaxes = [ templateVariable.refresh = 0;
{ }
show: panel['y-axis'], if (templateVariable.hideVariable) {
min: panel.grid.leftMin, templateVariable.hide = 2;
max: panel.grid.leftMax, } else if (templateVariable.hideLabel) {
logBase: panel.grid.leftLogBase, templateVariable.hide = 1;
format: panel.y_formats[0], }
label: panel.leftYAxisLabel, });
}, }
{
show: panel['y-axis'],
min: panel.grid.rightMin,
max: panel.grid.rightMax,
logBase: panel.grid.rightLogBase,
format: panel.y_formats[1],
label: panel.rightYAxisLabel,
}
];
panel.xaxis = {
show: panel['x-axis'],
};
delete panel.grid.leftMin;
delete panel.grid.leftMax;
delete panel.grid.leftLogBase;
delete panel.grid.rightMin;
delete panel.grid.rightMax;
delete panel.grid.rightLogBase;
delete panel.y_formats;
delete panel.leftYAxisLabel;
delete panel.rightYAxisLabel;
delete panel['y-axis'];
delete panel['x-axis'];
}
});
}
if (oldVersion < 13) { if (oldVersion < 12) {
// update graph yaxes changes // update graph yaxes changes
panelUpgrades.push(function(panel) { panelUpgrades.push(function(panel) {
if (panel.type !== 'graph') { return; } if (panel.type !== 'graph') {
if (!panel.grid) { return; } return;
}
panel.thresholds = []; if (!panel.grid) {
var t1: any = {}, t2: any = {}; return;
}
if (panel.grid.threshold1 !== null) {
t1.value = panel.grid.threshold1; if (!panel.yaxes) {
if (panel.grid.thresholdLine) { panel.yaxes = [
t1.line = true; {
t1.lineColor = panel.grid.threshold1Color; show: panel['y-axis'],
t1.colorMode = 'custom'; min: panel.grid.leftMin,
} else { max: panel.grid.leftMax,
t1.fill = true; logBase: panel.grid.leftLogBase,
t1.fillColor = panel.grid.threshold1Color; format: panel.y_formats[0],
t1.colorMode = 'custom'; label: panel.leftYAxisLabel,
} },
{
show: panel['y-axis'],
min: panel.grid.rightMin,
max: panel.grid.rightMax,
logBase: panel.grid.rightLogBase,
format: panel.y_formats[1],
label: panel.rightYAxisLabel,
},
];
panel.xaxis = {
show: panel['x-axis'],
};
delete panel.grid.leftMin;
delete panel.grid.leftMax;
delete panel.grid.leftLogBase;
delete panel.grid.rightMin;
delete panel.grid.rightMax;
delete panel.grid.rightLogBase;
delete panel.y_formats;
delete panel.leftYAxisLabel;
delete panel.rightYAxisLabel;
delete panel['y-axis'];
delete panel['x-axis'];
}
});
}
if (oldVersion < 13) {
// update graph yaxes changes
panelUpgrades.push(function(panel) {
if (panel.type !== 'graph') {
return;
}
if (!panel.grid) {
return;
}
panel.thresholds = [];
var t1: any = {},
t2: any = {};
if (panel.grid.threshold1 !== null) {
t1.value = panel.grid.threshold1;
if (panel.grid.thresholdLine) {
t1.line = true;
t1.lineColor = panel.grid.threshold1Color;
t1.colorMode = 'custom';
} else {
t1.fill = true;
t1.fillColor = panel.grid.threshold1Color;
t1.colorMode = 'custom';
} }
}
if (panel.grid.threshold2 !== null) { if (panel.grid.threshold2 !== null) {
t2.value = panel.grid.threshold2; t2.value = panel.grid.threshold2;
if (panel.grid.thresholdLine) { if (panel.grid.thresholdLine) {
t2.line = true; t2.line = true;
t2.lineColor = panel.grid.threshold2Color; t2.lineColor = panel.grid.threshold2Color;
t2.colorMode = 'custom'; t2.colorMode = 'custom';
} else { } else {
t2.fill = true; t2.fill = true;
t2.fillColor = panel.grid.threshold2Color; t2.fillColor = panel.grid.threshold2Color;
t2.colorMode = 'custom'; t2.colorMode = 'custom';
}
} }
}
if (_.isNumber(t1.value)) { if (_.isNumber(t1.value)) {
if (_.isNumber(t2.value)) { if (_.isNumber(t2.value)) {
if (t1.value > t2.value) { if (t1.value > t2.value) {
t1.op = t2.op = 'lt'; t1.op = t2.op = 'lt';
panel.thresholds.push(t1); panel.thresholds.push(t1);
panel.thresholds.push(t2); panel.thresholds.push(t2);
} else {
t1.op = t2.op = 'gt';
panel.thresholds.push(t1);
panel.thresholds.push(t2);
}
} else { } else {
t1.op = 'gt'; t1.op = t2.op = 'gt';
panel.thresholds.push(t1); panel.thresholds.push(t1);
panel.thresholds.push(t2);
} }
} else {
t1.op = 'gt';
panel.thresholds.push(t1);
} }
}
delete panel.grid.threshold1; delete panel.grid.threshold1;
delete panel.grid.threshold1Color; delete panel.grid.threshold1Color;
delete panel.grid.threshold2; delete panel.grid.threshold2;
delete panel.grid.threshold2Color; delete panel.grid.threshold2Color;
delete panel.grid.thresholdLine; delete panel.grid.thresholdLine;
}); });
} }
if (oldVersion < 14) { if (oldVersion < 14) {
this.graphTooltip = old.sharedCrosshair ? 1 : 0; this.graphTooltip = old.sharedCrosshair ? 1 : 0;
} }
if (oldVersion < 16) { if (oldVersion < 16) {
this.upgradeToGridLayout(old); this.upgradeToGridLayout(old);
} }
if (panelUpgrades.length === 0) { if (panelUpgrades.length === 0) {
return; return;
} }
for (j = 0; j < this.panels.length; j++) { for (j = 0; j < this.panels.length; j++) {
for (k = 0; k < panelUpgrades.length; k++) { for (k = 0; k < panelUpgrades.length; k++) {
panelUpgrades[k].call(this, this.panels[j]); panelUpgrades[k].call(this, this.panels[j]);
}
} }
} }
}
upgradeToGridLayout(old) { upgradeToGridLayout(old) {
let yPos = 0; let yPos = 0;
//let rowIds = 1000; let widthFactor = GRID_COLUMN_COUNT / 12;
//let rowIds = 1000;
//
if (!old.rows) {
return;
}
for (let row of old.rows) {
let xPos = 0;
let height: any = row.height || 250;
// if (this.meta.keepRows) {
// this.panels.push({
// id: rowIds++,
// type: 'row',
// title: row.title,
// x: 0,
// y: yPos,
// height: 1,
// width: 12
// });
// //
// yPos += 1;
// }
if (!old.rows) { if (_.isString(height)) {
return; height = parseInt(height.replace('px', ''), 10);
} }
for (let row of old.rows) { const rowGridHeight = Math.ceil(height / GRID_CELL_HEIGHT);
let xPos = 0;
let height: any = row.height || 250;
// if (this.meta.keepRows) {
// this.panels.push({
// id: rowIds++,
// type: 'row',
// title: row.title,
// x: 0,
// y: yPos,
// height: 1,
// width: 12
// });
//
// yPos += 1;
// }
if (_.isString(height)) {
height = parseInt(height.replace('px', ''), 10);
}
const rowGridHeight = Math.ceil(height / CELL_HEIGHT);
for (let panel of row.panels) { for (let panel of row.panels) {
// should wrap to next row? const panelWidth = Math.floor(panel.span) * widthFactor;
if (xPos + panel.span >= 12) {
yPos += rowGridHeight;
}
panel.gridPos = { x: xPos, y: yPos, w: panel.span, h: rowGridHeight }; // should wrap to next row?
if (xPos + panelWidth >= GRID_COLUMN_COUNT) {
yPos += rowGridHeight;
}
delete panel.span; panel.gridPos = {x: xPos, y: yPos, w: panelWidth, h: rowGridHeight};
xPos += panel.gridPos.w; delete panel.span;
this.panels.push(new PanelModel(panel)); xPos += panel.gridPos.w;
}
yPos += rowGridHeight; this.panels.push(new PanelModel(panel));
} }
yPos += rowGridHeight;
} }
}
} }

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import ReactGridLayout from 'react-grid-layout'; import ReactGridLayout from 'react-grid-layout';
import {CELL_HEIGHT, CELL_VMARGIN} from '../dashboard_model'; import {GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT} from 'app/core/constants';
import {DashboardPanel} from './DashboardPanel'; import {DashboardPanel} from './DashboardPanel';
import {DashboardModel} from '../dashboard_model'; import {DashboardModel} from '../dashboard_model';
import {PanelContainer} from './PanelContainer'; import {PanelContainer} from './PanelContainer';
@ -9,7 +9,6 @@ import {PanelModel} from '../panel_model';
import classNames from 'classnames'; import classNames from 'classnames';
import sizeMe from 'react-sizeme'; import sizeMe from 'react-sizeme';
const COLUMN_COUNT = 12;
let lastGridWidth = 1200; let lastGridWidth = 1200;
function GridWrapper({size, layout, onLayoutChange, children, onResize, onResizeStop, onWidthChange}) { function GridWrapper({size, layout, onLayoutChange, children, onResize, onResizeStop, onWidthChange}) {
@ -32,9 +31,9 @@ function GridWrapper({size, layout, onLayoutChange, children, onResize, onResize
measureBeforeMount={false} measureBeforeMount={false}
containerPadding={[0, 0]} containerPadding={[0, 0]}
useCSSTransforms={false} useCSSTransforms={false}
margin={[CELL_VMARGIN, CELL_VMARGIN]} margin={[GRID_CELL_VMARGIN, GRID_CELL_VMARGIN]}
cols={COLUMN_COUNT} cols={GRID_COLUMN_COUNT}
rowHeight={CELL_HEIGHT} rowHeight={GRID_CELL_HEIGHT}
draggableHandle=".grid-drag-handle" draggableHandle=".grid-drag-handle"
layout={layout} layout={layout}
onResize={onResize} onResize={onResize}

@ -1,11 +1,9 @@
///<reference path="../../../headers/common.d.ts" />
import {coreModule} from 'app/core/core'; import {coreModule} from 'app/core/core';
var template = ` var template = `
<div class="gf-form-select-wrapper max-width-13"> <div class="gf-form-select-wrapper max-width-13">
<select class="gf-form-input" ng-model="model.repeat" ng-options="f.value as f.text for f in variables"> <select class="gf-form-input" ng-model="model.repeat" ng-options="f.value as f.text for f in variables" ng-change="optionChanged()">
<option value=""></option> <option value=""></option>
</div> </div>
`; `;
@ -29,6 +27,17 @@ function dashRepeatOptionDirective(variableSrv) {
} }
scope.variables.unshift({text: 'Disabled', value: null}); scope.variables.unshift({text: 'Disabled', value: null});
// if repeat is set and no direction set to horizontal
if (scope.panel.repeat && !scope.panel.repeatDirection) {
scope.panel.repeatDirection = 'h';
}
scope.optionChanged = function() {
if (scope.panel.repeat) {
scope.panel.repeatDirection = 'h';
}
};
} }
}; };
} }

@ -400,20 +400,126 @@ describe('DashboardModel', function() {
}); });
describe('updateSubmenuVisibility with hidden annotation toggle', function() { describe('updateSubmenuVisibility with hidden annotation toggle', function() {
var model; var dashboard;
beforeEach(function() { beforeEach(function() {
model = new DashboardModel({ dashboard = new DashboardModel({
annotations: { annotations: {
list: [{hide: true}] list: [{hide: true}]
} }
}); });
model.updateSubmenuVisibility(); dashboard.updateSubmenuVisibility();
}); });
it('should not enable submmenu', function() { it('should not enable submmenu', function() {
expect(model.meta.submenuEnabled).to.be(false); expect(dashboard.meta.submenuEnabled).to.be(false);
}); });
}); });
describe('given dashboard with panel repeat', function(ctx) {
var dashboard;
beforeEach(function() {
dashboard = new DashboardModel({
panels: [{id: 2, repeat: 'apps'}],
templating: {
list: [{
name: 'apps',
current: {
text: 'se1, se2, se3',
value: ['se1', 'se2', 'se3']
},
options: [
{text: 'se1', value: 'se1', selected: true},
{text: 'se2', value: 'se2', selected: true},
{text: 'se3', value: 'se3', selected: true},
{text: 'se4', value: 'se4', selected: false}
]
}]
}
});
dashboard.processRepeats();
});
it('should repeat panel 3 times', function() {
expect(dashboard.panels.length).to.be(3);
});
it('should mark panel repeated', function() {
expect(dashboard.panels[0].repeat).to.be('apps');
expect(dashboard.panels[1].repeatPanelId).to.be(2);
});
it('should set scopedVars on panels', function() {
expect(dashboard.panels[0].scopedVars.apps.value).to.be('se1');
expect(dashboard.panels[1].scopedVars.apps.value).to.be('se2');
expect(dashboard.panels[2].scopedVars.apps.value).to.be('se3');
});
describe('After a second iteration', function() {
var repeatedPanelAfterIteration1;
beforeEach(function() {
repeatedPanelAfterIteration1 = dashboard.panels[1];
dashboard.panels[0].fill = 10;
dashboard.processRepeats();
});
it('reused panel should copy properties from source', function() {
expect(dashboard.panels[1].fill).to.be(10);
});
it('should have same panel count', function() {
expect(dashboard.panels.length).to.be(3);
});
});
describe('After a second iteration with different variable', function() {
beforeEach(function() {
dashboard.templating.list.push({
name: 'server',
current: { text: 'se1, se2, se3', value: ['se1']},
options: [{text: 'se1', value: 'se1', selected: true}]
});
dashboard.panels[0].repeat = "server";
dashboard.processRepeats();
});
it('should remove scopedVars value for last variable', function() {
expect(dashboard.panels[0].scopedVars.apps).to.be(undefined);
});
it('should have new variable value in scopedVars', function() {
expect(dashboard.panels[0].scopedVars.server.value).to.be("se1");
});
});
describe('After a second iteration and selected values reduced', function() {
beforeEach(function() {
dashboard.templating.list[0].options[1].selected = false;
dashboard.processRepeats();
});
it('should clean up repeated panel', function() {
expect(dashboard.panels.length).to.be(2);
});
});
describe('After a second iteration and panel repeat is turned off', function() {
beforeEach(function() {
dashboard.panels[0].repeat = null;
dashboard.processRepeats();
});
it('should clean up repeated panel', function() {
expect(dashboard.panels.length).to.be(1);
});
it('should remove scoped vars from reused panel', function() {
expect(dashboard.panels[0].scopedVars).to.be(undefined);
});
});
});
}); });

@ -1,287 +0,0 @@
// import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
//
// import '../dashboard_srv';
// import {DynamicDashboardSrv} from '../dynamic_dashboard_srv';
//
// function dynamicDashScenario(desc, func) {
//
// describe.skip(desc, function() {
// var ctx: any = {};
//
// ctx.setup = function (setupFunc) {
//
// beforeEach(angularMocks.module('grafana.core'));
// beforeEach(angularMocks.module('grafana.services'));
// beforeEach(angularMocks.module(function($provide) {
// $provide.value('contextSrv', {
// user: { timezone: 'utc'}
// });
// }));
//
// beforeEach(angularMocks.inject(function(dashboardSrv) {
// ctx.dashboardSrv = dashboardSrv;
//
// var model = {
// rows: [],
// templating: { list: [] }
// };
//
// setupFunc(model);
// ctx.dash = ctx.dashboardSrv.create(model);
// ctx.dynamicDashboardSrv = new DynamicDashboardSrv();
// ctx.dynamicDashboardSrv.init(ctx.dash);
// ctx.dynamicDashboardSrv.process();
// ctx.rows = ctx.dash.rows;
// }));
// };
//
// func(ctx);
// });
// }
//
// dynamicDashScenario('given dashboard with panel repeat', function(ctx) {
// ctx.setup(function(dash) {
// dash.rows.push({
// panels: [{id: 2, repeat: 'apps'}]
// });
// dash.templating.list.push({
// name: 'apps',
// current: {
// text: 'se1, se2, se3',
// value: ['se1', 'se2', 'se3']
// },
// options: [
// {text: 'se1', value: 'se1', selected: true},
// {text: 'se2', value: 'se2', selected: true},
// {text: 'se3', value: 'se3', selected: true},
// {text: 'se4', value: 'se4', selected: false}
// ]
// });
// });
//
// it('should repeat panel one time', function() {
// expect(ctx.rows[0].panels.length).to.be(3);
// });
//
// it('should mark panel repeated', function() {
// expect(ctx.rows[0].panels[0].repeat).to.be('apps');
// expect(ctx.rows[0].panels[1].repeatPanelId).to.be(2);
// });
//
// it('should set scopedVars on panels', function() {
// expect(ctx.rows[0].panels[0].scopedVars.apps.value).to.be('se1');
// expect(ctx.rows[0].panels[1].scopedVars.apps.value).to.be('se2');
// expect(ctx.rows[0].panels[2].scopedVars.apps.value).to.be('se3');
// });
//
// describe('After a second iteration', function() {
// var repeatedPanelAfterIteration1;
//
// beforeEach(function() {
// repeatedPanelAfterIteration1 = ctx.rows[0].panels[1];
// ctx.rows[0].panels[0].fill = 10;
// ctx.dynamicDashboardSrv.process();
// });
//
// it('should have reused same panel instances', function() {
// expect(ctx.rows[0].panels[1]).to.be(repeatedPanelAfterIteration1);
// });
//
// it('reused panel should copy properties from source', function() {
// expect(ctx.rows[0].panels[1].fill).to.be(10);
// });
//
// it('should have same panel count', function() {
// expect(ctx.rows[0].panels.length).to.be(3);
// });
// });
//
// describe('After a second iteration with different variable', function() {
// beforeEach(function() {
// ctx.dash.templating.list.push({
// name: 'server',
// current: { text: 'se1, se2, se3', value: ['se1']},
// options: [{text: 'se1', value: 'se1', selected: true}]
// });
// ctx.rows[0].panels[0].repeat = "server";
// ctx.dynamicDashboardSrv.process();
// });
//
// it('should remove scopedVars value for last variable', function() {
// expect(ctx.rows[0].panels[0].scopedVars.apps).to.be(undefined);
// });
//
// it('should have new variable value in scopedVars', function() {
// expect(ctx.rows[0].panels[0].scopedVars.server.value).to.be("se1");
// });
// });
//
// describe('After a second iteration and selected values reduced', function() {
// beforeEach(function() {
// ctx.dash.templating.list[0].options[1].selected = false;
// ctx.dynamicDashboardSrv.process();
// });
//
// it('should clean up repeated panel', function() {
// expect(ctx.rows[0].panels.length).to.be(2);
// });
// });
//
// describe('After a second iteration and panel repeat is turned off', function() {
// beforeEach(function() {
// ctx.rows[0].panels[0].repeat = null;
// ctx.dynamicDashboardSrv.process();
// });
//
// it('should clean up repeated panel', function() {
// expect(ctx.rows[0].panels.length).to.be(1);
// });
//
// it('should remove scoped vars from reused panel', function() {
// expect(ctx.rows[0].panels[0].scopedVars).to.be(undefined);
// });
// });
//
// });
//
// dynamicDashScenario('given dashboard with row repeat', function(ctx) {
// ctx.setup(function(dash) {
// dash.rows.push({
// repeat: 'servers',
// panels: [{id: 2}]
// });
// dash.rows.push({panels: []});
// dash.templating.list.push({
// name: 'servers',
// current: {
// text: 'se1, se2',
// value: ['se1', 'se2']
// },
// options: [
// {text: 'se1', value: 'se1', selected: true},
// {text: 'se2', value: 'se2', selected: true},
// ]
// });
// });
//
// it('should repeat row one time', function() {
// expect(ctx.rows.length).to.be(3);
// });
//
// it('should keep panel ids on first row', function() {
// expect(ctx.rows[0].panels[0].id).to.be(2);
// });
//
// it('should keep first row as repeat', function() {
// expect(ctx.rows[0].repeat).to.be('servers');
// });
//
// it('should clear repeat field on repeated row', function() {
// expect(ctx.rows[1].repeat).to.be(null);
// });
//
// it('should add scopedVars to rows', function() {
// expect(ctx.rows[0].scopedVars.servers.value).to.be('se1');
// expect(ctx.rows[1].scopedVars.servers.value).to.be('se2');
// });
//
// it('should generate a repeartRowId based on repeat row index', function() {
// expect(ctx.rows[1].repeatRowId).to.be(1);
// expect(ctx.rows[1].repeatIteration).to.be(ctx.dynamicDashboardSrv.iteration);
// });
//
// it('should set scopedVars on row panels', function() {
// expect(ctx.rows[0].panels[0].scopedVars.servers.value).to.be('se1');
// expect(ctx.rows[1].panels[0].scopedVars.servers.value).to.be('se2');
// });
//
// describe('After a second iteration', function() {
// var repeatedRowAfterFirstIteration;
//
// beforeEach(function() {
// repeatedRowAfterFirstIteration = ctx.rows[1];
// ctx.rows[0].height = 500;
// ctx.dynamicDashboardSrv.process();
// });
//
// it('should still only have 2 rows', function() {
// expect(ctx.rows.length).to.be(3);
// });
//
// it.skip('should have updated props from source', function() {
// expect(ctx.rows[1].height).to.be(500);
// });
//
// it('should reuse row instance', function() {
// expect(ctx.rows[1]).to.be(repeatedRowAfterFirstIteration);
// });
// });
//
// describe('After a second iteration and selected values reduced', function() {
// beforeEach(function() {
// ctx.dash.templating.list[0].options[1].selected = false;
// ctx.dynamicDashboardSrv.process();
// });
//
// it('should remove repeated second row', function() {
// expect(ctx.rows.length).to.be(2);
// });
// });
// });
//
// dynamicDashScenario('given dashboard with row repeat and panel repeat', function(ctx) {
// ctx.setup(function(dash) {
// dash.rows.push({
// repeat: 'servers',
// panels: [{id: 2, repeat: 'metric'}]
// });
// dash.templating.list.push({
// name: 'servers',
// current: { text: 'se1, se2', value: ['se1', 'se2'] },
// options: [
// {text: 'se1', value: 'se1', selected: true},
// {text: 'se2', value: 'se2', selected: true},
// ]
// });
// dash.templating.list.push({
// name: 'metric',
// current: { text: 'm1, m2', value: ['m1', 'm2'] },
// options: [
// {text: 'm1', value: 'm1', selected: true},
// {text: 'm2', value: 'm2', selected: true},
// ]
// });
// });
//
// it('should repeat row one time', function() {
// expect(ctx.rows.length).to.be(2);
// });
//
// it('should repeat panel on both rows', function() {
// expect(ctx.rows[0].panels.length).to.be(2);
// expect(ctx.rows[1].panels.length).to.be(2);
// });
//
// it('should keep panel ids on first row', function() {
// expect(ctx.rows[0].panels[0].id).to.be(2);
// });
//
// it('should mark second row as repeated', function() {
// expect(ctx.rows[0].repeat).to.be('servers');
// });
//
// it('should clear repeat field on repeated row', function() {
// expect(ctx.rows[1].repeat).to.be(null);
// });
//
// it('should generate a repeartRowId based on repeat row index', function() {
// expect(ctx.rows[1].repeatRowId).to.be(1);
// });
//
// it('should set scopedVars on row panels', function() {
// expect(ctx.rows[0].panels[0].scopedVars.servers.value).to.be('se1');
// expect(ctx.rows[1].panels[0].scopedVars.servers.value).to.be('se2');
// });
//
// });

@ -3,7 +3,7 @@ import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import {appEvents, profiler} from 'app/core/core'; import {appEvents, profiler} from 'app/core/core';
import Remarkable from 'remarkable'; import Remarkable from 'remarkable';
import {CELL_HEIGHT, CELL_VMARGIN} from '../dashboard/dashboard_model'; import {GRID_CELL_HEIGHT, GRID_CELL_VMARGIN} from 'app/core/constants';
const TITLE_HEIGHT = 25; const TITLE_HEIGHT = 25;
const EMPTY_TITLE_HEIGHT = 9; const EMPTY_TITLE_HEIGHT = 9;
@ -163,7 +163,7 @@ export class PanelCtrl {
var fullscreenHeight = Math.floor(docHeight * 0.8); var fullscreenHeight = Math.floor(docHeight * 0.8);
this.containerHeight = this.editMode ? editHeight : fullscreenHeight; this.containerHeight = this.editMode ? editHeight : fullscreenHeight;
} else { } else {
this.containerHeight = this.panel.gridPos.h * CELL_HEIGHT + ((this.panel.gridPos.h-1) * CELL_VMARGIN); this.containerHeight = this.panel.gridPos.h * GRID_CELL_HEIGHT + ((this.panel.gridPos.h-1) * GRID_CELL_VMARGIN);
} }
this.height = this.containerHeight - (PANEL_BORDER + PANEL_PADDING + (this.panel.title ? TITLE_HEIGHT : EMPTY_TITLE_HEIGHT)); this.height = this.containerHeight - (PANEL_BORDER + PANEL_PADDING + (this.panel.title ? TITLE_HEIGHT : EMPTY_TITLE_HEIGHT));

@ -9,24 +9,24 @@
<span class="gf-form-label width-7">Description</span> <span class="gf-form-label width-7">Description</span>
<textarea class="gf-form-input width-25" rows="3" ng-model="ctrl.panel.description" placeholder="Panel description, supports markdown & links"></textarea> <textarea class="gf-form-input width-25" rows="3" ng-model="ctrl.panel.description" placeholder="Panel description, supports markdown & links"></textarea>
</div> </div>
<gf-form-switch class="gf-form" label-class="width-7" switch-class="max-width-6" label="Transparent" checked="ctrl.panel.transparent" on-change="ctrl.render()"></gf-form-switch>
</div> </div>
<div class="section gf-form-group"> <div class="section gf-form-group">
<h5 class="section-heading">Options</h5> <h5 class="section-heading">Repeat</h5>
<gf-form-switch class="gf-form" label-class="width-8" switch-class="max-width-6" label="Transparent" checked="ctrl.panel.transparent" on-change="ctrl.render()"></gf-form-switch>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-8">Repeat Panel</span> <span class="gf-form-label width-9">For each value of</span>
<dash-repeat-option model="ctrl.panel"></dash-repeat-option> <dash-repeat-option model="ctrl.panel"></dash-repeat-option>
</div> </div>
<div class="gf-form"> <div class="gf-form" ng-show="ctrl.panel.repeat">
<span class="gf-form-label width-8">Min width</span> <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]"> <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]">
<option value=""></option> <option value=""></option>
</select> </select>
</div> </div>
<div class="gf-form"> <div class="gf-form" ng-show="ctrl.panel.repeat">
<span class="gf-form-label width-8">Direction</span> <span class="gf-form-label width-9">Direction</span>
<select class="gf-form-input" ng-model="ctrl.panel.repeatDirection" ng-options="f for f in ['X', 'Y']"> <select class="gf-form-input" ng-model="ctrl.panel.repeatDirection" ng-options="f.value as f.text for f in [{value: 'v', text: 'Vertical'}, {value: 'h', text: 'Horizontal'}]">
<option value=""></option> <option value=""></option>
</select> </select>
</div> </div>

@ -21,8 +21,8 @@
"transparent": true, "transparent": true,
"type": "text", "type": "text",
"gridPos": { "gridPos": {
"w": 12, "w": 24,
"h": 2, "h": 3,
"x": 0, "x": 0,
"y": 0 "y": 0
} }
@ -42,7 +42,7 @@
"transparent": false, "transparent": false,
"type": "dashlist", "type": "dashlist",
"gridPos": { "gridPos": {
"w": 7, "w": 12,
"h": 17, "h": 17,
"x": 0, "x": 0,
"y": 6 "y": 6
@ -57,9 +57,9 @@
"transparent": false, "transparent": false,
"type": "pluginlist", "type": "pluginlist",
"gridPos": { "gridPos": {
"w": 5, "w": 12,
"h": 17, "h": 17,
"x": 7, "x": 12,
"y": 6 "y": 6
} }
} }

Loading…
Cancel
Save