mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
grid: minor progress on panel repeats
This commit is contained in:
parent
215d59865e
commit
8bb9d92a73
@ -51,8 +51,10 @@ import {userGroupPicker} from './components/user_group_picker';
|
||||
import {geminiScrollbar} from './components/scroll/scroll';
|
||||
import {gfPageDirective} from './components/gf_page';
|
||||
import {orgSwitcher} from './components/org_switcher';
|
||||
import {profiler} from './profiler';
|
||||
|
||||
export {
|
||||
profiler,
|
||||
arrayJoin,
|
||||
coreModule,
|
||||
grafanaAppDirective,
|
||||
|
@ -20,7 +20,6 @@ export class DashboardCtrl implements PanelContainer {
|
||||
private alertingSrv,
|
||||
private dashboardSrv,
|
||||
private unsavedChangesSrv,
|
||||
private dynamicDashboardSrv,
|
||||
private dashboardViewStateSrv,
|
||||
private panelLoader) {
|
||||
// temp hack due to way dashboards are loaded
|
||||
@ -57,10 +56,9 @@ export class DashboardCtrl implements PanelContainer {
|
||||
.catch(this.onInitFailed.bind(this, 'Templating init failed', false))
|
||||
// continue
|
||||
.finally(() => {
|
||||
this.dashboard = dashboard;
|
||||
|
||||
this.dynamicDashboardSrv.init(dashboard);
|
||||
this.dynamicDashboardSrv.process();
|
||||
this.dashboard = dashboard;
|
||||
this.dashboard.processRepeats();
|
||||
|
||||
this.unsavedChangesSrv.init(dashboard, this.$scope);
|
||||
|
||||
@ -97,7 +95,7 @@ export class DashboardCtrl implements PanelContainer {
|
||||
}
|
||||
|
||||
templateVariableUpdated() {
|
||||
this.dynamicDashboardSrv.process();
|
||||
this.dashboard.processRepeats();
|
||||
}
|
||||
|
||||
setWindowTitleAndTheme() {
|
||||
|
@ -2,7 +2,7 @@ import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
|
||||
import {DEFAULT_ANNOTATION_COLOR} from 'app/core/utils/colors';
|
||||
import {Emitter, contextSrv, appEvents} 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';
|
||||
@ -34,12 +34,19 @@ export class DashboardModel {
|
||||
revision: number;
|
||||
links: any;
|
||||
gnetId: any;
|
||||
meta: any;
|
||||
events: any;
|
||||
editMode: boolean;
|
||||
folderId: number;
|
||||
panels: PanelModel[];
|
||||
|
||||
// ------------------
|
||||
// not persisted
|
||||
// ------------------
|
||||
|
||||
// repeat process cycles
|
||||
iteration: number;
|
||||
meta: any;
|
||||
events: Emitter;
|
||||
|
||||
static nonPersistedProperties: {[str: string]: boolean} = {
|
||||
"events": true,
|
||||
"meta": true,
|
||||
@ -193,7 +200,12 @@ export class DashboardModel {
|
||||
|
||||
this.panels.unshift(new PanelModel(panel));
|
||||
|
||||
// make sure it's sorted by pos
|
||||
this.sortPanelsByGridPos();
|
||||
|
||||
this.events.emit('panel-added', panel);
|
||||
}
|
||||
|
||||
private sortPanelsByGridPos() {
|
||||
this.panels.sort(function(panelA, panelB) {
|
||||
if (panelA.gridPos.y === panelB.gridPos.y) {
|
||||
return panelA.gridPos.x - panelB.gridPos.x;
|
||||
@ -201,33 +213,86 @@ export class DashboardModel {
|
||||
return panelA.gridPos.y - panelB.gridPos.y;
|
||||
}
|
||||
});
|
||||
|
||||
this.events.emit('panel-added', panel);
|
||||
}
|
||||
|
||||
removePanel(panel, ask?) {
|
||||
// confirm deletion
|
||||
if (ask !== false) {
|
||||
var text2, confirmText;
|
||||
if (panel.alert) {
|
||||
text2 = "Panel includes an alert rule, removing panel will also remove alert rule";
|
||||
confirmText = "YES";
|
||||
}
|
||||
cleanUpRepeats() {
|
||||
this.processRepeats(true);
|
||||
}
|
||||
|
||||
appEvents.emit('confirm-modal', {
|
||||
title: 'Remove Panel',
|
||||
text: 'Are you sure you want to remove this panel?',
|
||||
text2: text2,
|
||||
icon: 'fa-trash',
|
||||
confirmText: confirmText,
|
||||
yesText: 'Remove',
|
||||
onConfirm: () => {
|
||||
this.removePanel(panel, false);
|
||||
}
|
||||
});
|
||||
processRepeats(cleanUpOnly?: boolean) {
|
||||
if (this.snapshot || this.templating.list.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.iteration = (this.iteration || new Date().getTime()) + 1;
|
||||
|
||||
let panelsToRemove = [];
|
||||
|
||||
for (let panel of this.panels) {
|
||||
if (panel.repeat) {
|
||||
if (!cleanUpOnly) {
|
||||
this.repeatPanel(panel);
|
||||
}
|
||||
} else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) {
|
||||
panelsToRemove.push(panel);
|
||||
}
|
||||
}
|
||||
|
||||
// remove panels
|
||||
_.pull(this.panels, ...panelsToRemove);
|
||||
|
||||
this.sortPanelsByGridPos();
|
||||
this.events.emit('repeats-processed');
|
||||
}
|
||||
|
||||
getRepeatClone(sourcePanel, index) {
|
||||
// if first clone return source
|
||||
if (index === 0) {
|
||||
return sourcePanel;
|
||||
}
|
||||
|
||||
var clone = new PanelModel(sourcePanel.getSaveModel());
|
||||
clone.id = this.getNextPanelId();
|
||||
this.panels.push(clone);
|
||||
|
||||
clone.repeatIteration = this.iteration;
|
||||
clone.repeatPanelId = sourcePanel.id;
|
||||
clone.repeat = null;
|
||||
return clone;
|
||||
}
|
||||
|
||||
repeatPanel(panel: PanelModel) {
|
||||
var variable = _.find(this.templating.list, {name: panel.repeat});
|
||||
if (!variable) { return; }
|
||||
|
||||
var selected;
|
||||
if (variable.current.text === 'All') {
|
||||
selected = variable.options.slice(1, variable.options.length);
|
||||
} else {
|
||||
selected = _.filter(variable.options, {selected: true});
|
||||
}
|
||||
|
||||
for (let index = 0; index < selected.length; index++) {
|
||||
var option = selected[index];
|
||||
var copy = this.getRepeatClone(panel, index);
|
||||
|
||||
copy.scopedVars = copy.scopedVars || {};
|
||||
copy.scopedVars[variable.name] = option;
|
||||
|
||||
// souce panel uses original possition
|
||||
if (index === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (panel.repeatDirection === 'Y') {
|
||||
copy.gridPos.y = panel.gridPos.y + (panel.gridPos.h*index);
|
||||
} else {
|
||||
copy.gridPos.x = panel.gridPos.x + (panel.gridPos.w*index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removePanel(panel: PanelModel) {
|
||||
var index = _.indexOf(this.panels, panel);
|
||||
this.panels.splice(index, 1);
|
||||
this.events.emit('panel-removed', panel);
|
||||
|
@ -69,6 +69,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
|
||||
this.dashboard = this.panelContainer.getDashboard();
|
||||
this.dashboard.on('panel-added', this.triggerForceUpdate.bind(this));
|
||||
this.dashboard.on('panel-removed', this.triggerForceUpdate.bind(this));
|
||||
this.dashboard.on('repeats-processed', this.triggerForceUpdate.bind(this));
|
||||
this.dashboard.on('view-mode-changed', this.triggerForceUpdate.bind(this));
|
||||
}
|
||||
|
||||
|
@ -1,192 +0,0 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
import coreModule from 'app/core/core_module';
|
||||
import {DashboardRow} from './row/row_model';
|
||||
|
||||
export class DynamicDashboardSrv {
|
||||
iteration: number;
|
||||
dashboard: any;
|
||||
variables: any;
|
||||
|
||||
init(dashboard) {
|
||||
this.dashboard = dashboard;
|
||||
this.variables = dashboard.templating.list;
|
||||
}
|
||||
|
||||
process(options?) {
|
||||
if (this.dashboard.snapshot || this.variables.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.iteration = (this.iteration || new Date().getTime()) + 1;
|
||||
|
||||
options = options || {};
|
||||
var cleanUpOnly = options.cleanUpOnly;
|
||||
var i, j, row, panel;
|
||||
|
||||
if (this.dashboard.rows) {
|
||||
// cleanup scopedVars
|
||||
for (i = 0; i < this.dashboard.rows.length; i++) {
|
||||
row = this.dashboard.rows[i];
|
||||
delete row.scopedVars;
|
||||
|
||||
for (j = 0; j < row.panels.length; j++) {
|
||||
delete row.panels[j].scopedVars;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < this.dashboard.rows.length; i++) {
|
||||
row = this.dashboard.rows[i];
|
||||
|
||||
// handle row repeats
|
||||
if (row.repeat) {
|
||||
if (!cleanUpOnly) {
|
||||
this.repeatRow(row, i);
|
||||
}
|
||||
} else if (row.repeatRowId && row.repeatIteration !== this.iteration) {
|
||||
// clean up old left overs
|
||||
this.dashboard.removeRow(row, true);
|
||||
i = i - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// repeat panels
|
||||
for (j = 0; j < row.panels.length; j++) {
|
||||
panel = row.panels[j];
|
||||
if (panel.repeat) {
|
||||
if (!cleanUpOnly) {
|
||||
this.repeatPanel(panel, row);
|
||||
}
|
||||
} else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) {
|
||||
// clean up old left overs
|
||||
row.panels = _.without(row.panels, panel);
|
||||
j = j - 1;
|
||||
}
|
||||
}
|
||||
|
||||
row.panelSpanChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns a new row clone or reuses a clone from previous iteration
|
||||
getRowClone(sourceRow, repeatIndex, sourceRowIndex) {
|
||||
if (repeatIndex === 0) {
|
||||
return sourceRow;
|
||||
}
|
||||
|
||||
var i, panel, row, copy;
|
||||
var sourceRowId = sourceRowIndex + 1;
|
||||
|
||||
// look for row to reuse
|
||||
for (i = 0; i < this.dashboard.rows.length; i++) {
|
||||
row = this.dashboard.rows[i];
|
||||
if (row.repeatRowId === sourceRowId && row.repeatIteration !== this.iteration) {
|
||||
copy = row;
|
||||
copy.copyPropertiesFromRowSource(sourceRow);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!copy) {
|
||||
var modelCopy = angular.copy(sourceRow.getSaveModel());
|
||||
copy = new DashboardRow(modelCopy);
|
||||
this.dashboard.rows.splice(sourceRowIndex + repeatIndex, 0, copy);
|
||||
|
||||
// set new panel ids
|
||||
for (i = 0; i < copy.panels.length; i++) {
|
||||
panel = copy.panels[i];
|
||||
panel.id = this.dashboard.getNextPanelId();
|
||||
}
|
||||
}
|
||||
|
||||
copy.repeat = null;
|
||||
copy.repeatRowId = sourceRowId;
|
||||
copy.repeatIteration = this.iteration;
|
||||
return copy;
|
||||
}
|
||||
|
||||
// returns a new row clone or reuses a clone from previous iteration
|
||||
repeatRow(row, rowIndex) {
|
||||
var variable = _.find(this.variables, {name: row.repeat});
|
||||
if (!variable) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selected, copy, i, panel;
|
||||
if (variable.current.text === 'All') {
|
||||
selected = variable.options.slice(1, variable.options.length);
|
||||
} else {
|
||||
selected = _.filter(variable.options, {selected: true});
|
||||
}
|
||||
|
||||
_.each(selected, (option, index) => {
|
||||
copy = this.getRowClone(row, index, rowIndex);
|
||||
copy.scopedVars = {};
|
||||
copy.scopedVars[variable.name] = option;
|
||||
|
||||
for (i = 0; i < copy.panels.length; i++) {
|
||||
panel = copy.panels[i];
|
||||
panel.scopedVars = {};
|
||||
panel.scopedVars[variable.name] = option;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPanelClone(sourcePanel, row, index) {
|
||||
// if first clone return source
|
||||
if (index === 0) {
|
||||
return sourcePanel;
|
||||
}
|
||||
|
||||
var i, tmpId, panel, clone;
|
||||
|
||||
// first try finding an existing clone to use
|
||||
for (i = 0; i < row.panels.length; i++) {
|
||||
panel = row.panels[i];
|
||||
if (panel.repeatIteration !== this.iteration && panel.repeatPanelId === sourcePanel.id) {
|
||||
clone = panel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clone) {
|
||||
clone = { id: this.dashboard.getNextPanelId() };
|
||||
row.panels.push(clone);
|
||||
}
|
||||
|
||||
// save id
|
||||
tmpId = clone.id;
|
||||
// copy properties from source
|
||||
angular.copy(sourcePanel, clone);
|
||||
// restore id
|
||||
clone.id = tmpId;
|
||||
clone.repeatIteration = this.iteration;
|
||||
clone.repeatPanelId = sourcePanel.id;
|
||||
clone.repeat = null;
|
||||
return clone;
|
||||
}
|
||||
|
||||
repeatPanel(panel, row) {
|
||||
var variable = _.find(this.variables, {name: panel.repeat});
|
||||
if (!variable) { return; }
|
||||
|
||||
var selected;
|
||||
if (variable.current.text === 'All') {
|
||||
selected = variable.options.slice(1, variable.options.length);
|
||||
} else {
|
||||
selected = _.filter(variable.options, {selected: true});
|
||||
}
|
||||
|
||||
_.each(selected, (option, index) => {
|
||||
var copy = this.getPanelClone(panel, row, index);
|
||||
copy.span = Math.max(12 / selected.length, panel.minSpan || 4);
|
||||
copy.scopedVars = copy.scopedVars || {};
|
||||
copy.scopedVars[variable.name] = option;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.service('dynamicDashboardSrv', DynamicDashboardSrv);
|
||||
|
@ -1,29 +1,24 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import config from 'app/core/config';
|
||||
import _ from 'lodash';
|
||||
import {DynamicDashboardSrv} from '../dynamic_dashboard_srv';
|
||||
import {DashboardModel} from '../dashboard_model';
|
||||
|
||||
export class DashboardExporter {
|
||||
|
||||
constructor(private datasourceSrv) {
|
||||
}
|
||||
|
||||
makeExportable(dashboard) {
|
||||
var dynSrv = new DynamicDashboardSrv();
|
||||
|
||||
makeExportable(dashboard: DashboardModel) {
|
||||
// clean up repeated rows and panels,
|
||||
// this is done on the live real dashboard instance, not on a clone
|
||||
// so we need to undo this
|
||||
// this is pretty hacky and needs to be changed
|
||||
dynSrv.init(dashboard);
|
||||
dynSrv.process({cleanUpOnly: true});
|
||||
dashboard.cleanUpRepeats();
|
||||
|
||||
var saveModel = dashboard.getSaveModelClone();
|
||||
saveModel.id = null;
|
||||
|
||||
// undo repeat cleanup
|
||||
dynSrv.process();
|
||||
dashboard.processRepeats();
|
||||
|
||||
var inputs = [];
|
||||
var requires = {};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {Emitter} from 'app/core/core';
|
||||
import _ from 'lodash';
|
||||
|
||||
export interface GridPos {
|
||||
x: number;
|
||||
@ -21,6 +22,9 @@ export class PanelModel {
|
||||
alert?: any;
|
||||
scopedVars?: any;
|
||||
repeat?: any;
|
||||
repeatIteration?: any;
|
||||
repeatPanelId?: any;
|
||||
repeatDirection?: any;
|
||||
|
||||
// non persisted
|
||||
fullscreen: boolean;
|
||||
@ -34,6 +38,10 @@ export class PanelModel {
|
||||
for (var property in model) {
|
||||
this[property] = model[property];
|
||||
}
|
||||
|
||||
if (!this.gridPos) {
|
||||
this.gridPos = {x: 0, y: 0, h: 3, w: 6};
|
||||
}
|
||||
}
|
||||
|
||||
getSaveModel() {
|
||||
@ -43,7 +51,7 @@ export class PanelModel {
|
||||
continue;
|
||||
}
|
||||
|
||||
model[property] = this[property];
|
||||
model[property] = _.cloneDeep(this[property]);
|
||||
}
|
||||
|
||||
return model;
|
||||
|
@ -1,287 +1,287 @@
|
||||
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');
|
||||
});
|
||||
|
||||
});
|
||||
// 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');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
|
||||
|
@ -10,7 +10,6 @@ describe('given dashboard with repeated panels', function() {
|
||||
|
||||
beforeEach(done => {
|
||||
dash = {
|
||||
rows: [],
|
||||
templating: { list: [] },
|
||||
annotations: { list: [] },
|
||||
};
|
||||
@ -47,26 +46,6 @@ describe('given dashboard with repeated panels', function() {
|
||||
datasource: 'gfdb',
|
||||
});
|
||||
|
||||
dash.rows.push({
|
||||
repeat: 'test',
|
||||
panels: [
|
||||
{id: 2, repeat: 'apps', datasource: 'gfdb', type: 'graph'},
|
||||
{id: 3, repeat: null, repeatPanelId: 2},
|
||||
{
|
||||
id: 4,
|
||||
datasource: '-- Mixed --',
|
||||
targets: [{datasource: 'other'}],
|
||||
},
|
||||
{id: 5, datasource: '$ds'},
|
||||
]
|
||||
});
|
||||
|
||||
dash.rows.push({
|
||||
repeat: null,
|
||||
repeatRowId: 1,
|
||||
panels: [],
|
||||
});
|
||||
|
||||
dash.panels = [
|
||||
{id: 6, datasource: 'gfdb', type: 'graph'},
|
||||
{id: 7},
|
||||
@ -78,6 +57,9 @@ describe('given dashboard with repeated panels', function() {
|
||||
{id: 9, datasource: '$ds'},
|
||||
];
|
||||
|
||||
dash.panels.push({id: 2, repeat: 'apps', datasource: 'gfdb', type: 'graph'});
|
||||
dash.panels.push({id: 3, repeat: null, repeatPanelId: 2});
|
||||
|
||||
var datasourceSrvStub = {get: sinon.stub()};
|
||||
datasourceSrvStub.get.withArgs('gfdb').returns(Promise.resolve({
|
||||
name: 'gfdb',
|
||||
@ -110,14 +92,6 @@ describe('given dashboard with repeated panels', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('exported dashboard should not contain repeated panels', function() {
|
||||
expect(exported.rows[0].panels.length).to.be(3);
|
||||
});
|
||||
|
||||
it.skip('exported dashboard should not contain repeated rows', function() {
|
||||
expect(exported.rows.length).to.be(1);
|
||||
});
|
||||
|
||||
it('should replace datasource refs', function() {
|
||||
var panel = exported.panels[0];
|
||||
expect(panel.datasource).to.be("${DS_GFDB}");
|
||||
|
@ -22,9 +22,9 @@ const template = `
|
||||
</div>
|
||||
|
||||
<div class="confirm-modal-buttons">
|
||||
<button type="button" class="btn btn-inverse" ng-click="ctrl.dismiss()">Cancel</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="ctrl.discard()">Discard</button>
|
||||
<button type="button" class="btn btn-success" ng-click="ctrl.save()">Save</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="ctrl.discard()">Discard</button>
|
||||
<button type="button" class="btn btn-inverse" ng-click="ctrl.dismiss()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import config from 'app/core/config';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import {profiler} from 'app/core/profiler';
|
||||
import {appEvents, profiler} from 'app/core/core';
|
||||
import Remarkable from 'remarkable';
|
||||
import {CELL_HEIGHT, CELL_VMARGIN} from '../dashboard/dashboard_model';
|
||||
|
||||
@ -188,7 +188,30 @@ export class PanelCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
removePanel() {
|
||||
removePanel(ask: boolean) {
|
||||
// confirm deletion
|
||||
if (ask !== false) {
|
||||
var text2, confirmText;
|
||||
|
||||
if (this.panel.alert) {
|
||||
text2 = "Panel includes an alert rule, removing panel will also remove alert rule";
|
||||
confirmText = "YES";
|
||||
}
|
||||
|
||||
appEvents.emit('confirm-modal', {
|
||||
title: 'Remove Panel',
|
||||
text: 'Are you sure you want to remove this panel?',
|
||||
text2: text2,
|
||||
icon: 'fa-trash',
|
||||
confirmText: confirmText,
|
||||
yesText: 'Remove',
|
||||
onConfirm: () => {
|
||||
this.removePanel(false);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.dashboard.removePanel(this.panel);
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,12 @@
|
||||
<option value=""></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-8">Direction</span>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.repeatDirection" ng-options="f for f in ['X', 'Y']">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<panel-links-editor panel="ctrl.panel"></panel-links-editor>
|
||||
|
Loading…
Reference in New Issue
Block a user