Commit 37f9bdfc by Patrick O'Carroll Committed by Marcus Efraimsson

save modal ux improvements (#11822)

changes to save modal when saving an updated dashboard

Changed time range and variables are now not saved by default, 
you'll need to actively choose if you want to save updated time 
range and or variables.
parent 938deae4
...@@ -22,8 +22,10 @@ export class DashboardModel { ...@@ -22,8 +22,10 @@ export class DashboardModel {
editable: any; editable: any;
graphTooltip: any; graphTooltip: any;
time: any; time: any;
originalTime: any;
timepicker: any; timepicker: any;
templating: any; templating: any;
originalTemplating: any;
annotations: any; annotations: any;
refresh: any; refresh: any;
snapshot: any; snapshot: any;
...@@ -68,8 +70,12 @@ export class DashboardModel { ...@@ -68,8 +70,12 @@ export class DashboardModel {
this.editable = data.editable !== false; this.editable = data.editable !== false;
this.graphTooltip = data.graphTooltip || 0; this.graphTooltip = data.graphTooltip || 0;
this.time = data.time || { from: 'now-6h', to: 'now' }; this.time = data.time || { from: 'now-6h', to: 'now' };
this.originalTime = _.cloneDeep(this.time);
this.timepicker = data.timepicker || {}; this.timepicker = data.timepicker || {};
this.templating = this.ensureListExist(data.templating); this.templating = this.ensureListExist(data.templating);
this.originalTemplating = _.map(this.templating.list, variable => {
return { name: variable.name, current: _.clone(variable.current) };
});
this.annotations = this.ensureListExist(data.annotations); this.annotations = this.ensureListExist(data.annotations);
this.refresh = data.refresh; this.refresh = data.refresh;
this.snapshot = data.snapshot; this.snapshot = data.snapshot;
...@@ -130,7 +136,12 @@ export class DashboardModel { ...@@ -130,7 +136,12 @@ export class DashboardModel {
} }
// cleans meta data and other non persistent state // cleans meta data and other non persistent state
getSaveModelClone() { getSaveModelClone(options?) {
let defaults = _.defaults(options || {}, {
saveVariables: false,
saveTimerange: false,
});
// make clone // make clone
var copy: any = {}; var copy: any = {};
for (var property in this) { for (var property in this) {
...@@ -142,10 +153,23 @@ export class DashboardModel { ...@@ -142,10 +153,23 @@ export class DashboardModel {
} }
// get variable save models // get variable save models
//console.log(this.templating.list);
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)),
}; };
if (!defaults.saveVariables && copy.templating.list.length === this.originalTemplating.length) {
for (let i = 0; i < copy.templating.list.length; i++) {
if (copy.templating.list[i].name === this.originalTemplating[i].name) {
copy.templating.list[i].current = this.originalTemplating[i].current;
}
}
}
if (!defaults.saveTimerange) {
copy.time = this.originalTime;
}
// get panel save models // get panel save models
copy.panels = _.chain(this.panels) copy.panels = _.chain(this.panels)
.filter(panel => panel.type !== 'add-panel') .filter(panel => panel.type !== 'add-panel')
......
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import _ from 'lodash';
const template = ` const template = `
<div class="modal-body"> <div class="modal-body">
...@@ -14,19 +15,29 @@ const template = ` ...@@ -14,19 +15,29 @@ const template = `
</div> </div>
<form name="ctrl.saveForm" ng-submit="ctrl.save()" class="modal-content" novalidate> <form name="ctrl.saveForm" ng-submit="ctrl.save()" class="modal-content" novalidate>
<h6 class="text-center">Add a note to describe your changes</h6> <div class="p-t-1">
<div class="p-t-2"> <div class="gf-form-group" ng-if="ctrl.timeChange || ctrl.variableChange">
<gf-form-switch class="gf-form"
label="Save current time range" ng-if="ctrl.timeChange" label-class="width-12" switch-class="max-width-6"
checked="ctrl.saveTimerange" on-change="buildUrl()">
</gf-form-switch>
<gf-form-switch class="gf-form"
label="Save current variables" ng-if="ctrl.variableChange" label-class="width-12" switch-class="max-width-6"
checked="ctrl.saveVariables" on-change="buildUrl()">
</gf-form-switch>
</div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-hint"> <label class="gf-form-hint">
<input <input
type="text" type="text"
name="message" name="message"
class="gf-form-input" class="gf-form-input"
placeholder="Updates to &hellip;" placeholder="Add a note to describe your changes &hellip;"
give-focus="true" give-focus="true"
ng-model="ctrl.message" ng-model="ctrl.message"
ng-model-options="{allowInvalid: true}" ng-model-options="{allowInvalid: true}"
ng-maxlength="this.max" ng-maxlength="this.max"
maxlength="64"
autocomplete="off" /> autocomplete="off" />
<small class="gf-form-hint-text muted" ng-cloak> <small class="gf-form-hint-text muted" ng-cloak>
<span ng-class="{'text-error': ctrl.saveForm.message.$invalid && ctrl.saveForm.message.$dirty }"> <span ng-class="{'text-error': ctrl.saveForm.message.$invalid && ctrl.saveForm.message.$dirty }">
...@@ -40,7 +51,7 @@ const template = ` ...@@ -40,7 +51,7 @@ const template = `
<div class="gf-form-button-row text-center"> <div class="gf-form-button-row text-center">
<button type="submit" class="btn btn-success" ng-disabled="ctrl.saveForm.$invalid">Save</button> <button type="submit" class="btn btn-success" ng-disabled="ctrl.saveForm.$invalid">Save</button>
<button class="btn btn-inverse" ng-click="ctrl.dismiss();">Cancel</button> <a class="btn btn-link" ng-click="ctrl.dismiss();">Cancel</a>
</div> </div>
</form> </form>
</div> </div>
...@@ -48,14 +59,51 @@ const template = ` ...@@ -48,14 +59,51 @@ const template = `
export class SaveDashboardModalCtrl { export class SaveDashboardModalCtrl {
message: string; message: string;
saveVariables = false;
saveTimerange = false;
templating: any;
time: any;
originalTime: any;
current = [];
originalCurrent = [];
max: number; max: number;
saveForm: any; saveForm: any;
dismiss: () => void; dismiss: () => void;
timeChange = false;
variableChange = false;
/** @ngInject */ /** @ngInject */
constructor(private dashboardSrv) { constructor(private dashboardSrv) {
this.message = ''; this.message = '';
this.max = 64; this.max = 64;
this.templating = dashboardSrv.dash.templating.list;
this.compareTemplating();
this.compareTime();
}
compareTime() {
if (_.isEqual(this.dashboardSrv.dash.time, this.dashboardSrv.dash.originalTime)) {
this.timeChange = false;
} else {
this.timeChange = true;
}
}
compareTemplating() {
if (this.dashboardSrv.dash.templating.list.length > 0) {
for (let i = 0; i < this.dashboardSrv.dash.templating.list.length; i++) {
if (
this.dashboardSrv.dash.templating.list[i].current.text !==
this.dashboardSrv.dash.originalTemplating[i].current.text
) {
return (this.variableChange = true);
}
}
return (this.variableChange = false);
} else {
return (this.variableChange = false);
}
} }
save() { save() {
...@@ -63,9 +111,14 @@ export class SaveDashboardModalCtrl { ...@@ -63,9 +111,14 @@ export class SaveDashboardModalCtrl {
return; return;
} }
var options = {
saveVariables: this.saveVariables,
saveTimerange: this.saveTimerange,
message: this.message,
};
var dashboard = this.dashboardSrv.getCurrent(); var dashboard = this.dashboardSrv.getCurrent();
var saveModel = dashboard.getSaveModelClone(); var saveModel = dashboard.getSaveModelClone(options);
var options = { message: this.message };
return this.dashboardSrv.save(saveModel, options).then(this.dismiss); return this.dashboardSrv.save(saveModel, options).then(this.dismiss);
} }
......
...@@ -434,4 +434,63 @@ describe('DashboardModel', function() { ...@@ -434,4 +434,63 @@ describe('DashboardModel', function() {
}); });
}); });
}); });
describe('save variables and timeline', () => {
let model;
beforeEach(() => {
model = new DashboardModel({
templating: {
list: [
{
name: 'Server',
current: {
selected: true,
text: 'server_001',
value: 'server_001',
},
},
],
},
time: {
from: 'now-6h',
to: 'now',
},
});
model.templating.list[0] = {
name: 'Server',
current: {
selected: true,
text: 'server_002',
value: 'server_002',
},
};
model.time = {
from: 'now-3h',
to: 'now',
};
});
it('should not save variables and timeline', () => {
let options = {
saveVariables: false,
saveTimerange: false,
};
let saveModel = model.getSaveModelClone(options);
expect(saveModel.templating.list[0].current.text).toBe('server_001');
expect(saveModel.time.from).toBe('now-6h');
});
it('should save variables and timeline', () => {
let options = {
saveVariables: true,
saveTimerange: true,
};
let saveModel = model.getSaveModelClone(options);
expect(saveModel.templating.list[0].current.text).toBe('server_002');
expect(saveModel.time.from).toBe('now-3h');
});
});
}); });
import { SaveDashboardModalCtrl } from '../save_modal';
jest.mock('app/core/services/context_srv', () => ({}));
describe('SaveDashboardModal', () => {
describe('save modal checkboxes', () => {
it('should show checkboxes', () => {
let fakeDashboardSrv = {
dash: {
templating: {
list: [
{
current: {
selected: true,
tags: Array(0),
text: 'server_001',
value: 'server_001',
},
name: 'Server',
},
],
},
originalTemplating: [
{
current: {
selected: true,
text: 'server_002',
value: 'server_002',
},
name: 'Server',
},
],
time: {
from: 'now-3h',
to: 'now',
},
originalTime: {
from: 'now-6h',
to: 'now',
},
},
};
let modal = new SaveDashboardModalCtrl(fakeDashboardSrv);
expect(modal.timeChange).toBe(true);
expect(modal.variableChange).toBe(true);
});
it('should hide checkboxes', () => {
let fakeDashboardSrv = {
dash: {
templating: {
list: [
{
current: {
selected: true,
//tags: Array(0),
text: 'server_002',
value: 'server_002',
},
name: 'Server',
},
],
},
originalTemplating: [
{
current: {
selected: true,
text: 'server_002',
value: 'server_002',
},
name: 'Server',
},
],
time: {
from: 'now-3h',
to: 'now',
},
originalTime: {
from: 'now-3h',
to: 'now',
},
},
};
let modal = new SaveDashboardModalCtrl(fakeDashboardSrv);
expect(modal.timeChange).toBe(false);
expect(modal.variableChange).toBe(false);
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment