mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
A lot of refactoring opf unsaved changes service so it can be unit tested better
This commit is contained in:
47
'
Normal file
47
'
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
define([
|
||||||
|
'features/dashboard/unsavedChangesSrv',
|
||||||
|
'features/dashboard/dashboardSrv'
|
||||||
|
], function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("unsavedChangesSrv", function() {
|
||||||
|
var _unsavedChangesSrv;
|
||||||
|
var _dashboardSrv;
|
||||||
|
var _location;
|
||||||
|
var _contextSrvStub = {
|
||||||
|
isEditor: true
|
||||||
|
};
|
||||||
|
var _rootScope;
|
||||||
|
var tracker;
|
||||||
|
|
||||||
|
beforeEach(module('grafana.services'));
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('contextSrv', _contextSrvStub);
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(inject(function(unsavedChangesSrv, $location, $rootScope, dashboardSrv) {
|
||||||
|
_unsavedChangesSrv = unsavedChangesSrv;
|
||||||
|
_dashboardSrv = dashboardSrv;
|
||||||
|
_location = $location;
|
||||||
|
_rootScope = $rootScope;
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('when dashboard is modified and route changes', function() {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
var dash = _dashboardSrv.create({});
|
||||||
|
var scope = _rootScope.$new();
|
||||||
|
scope.appEvent = sinon.spy();
|
||||||
|
scope.onAppEvent = sinon.spy();
|
||||||
|
tracker = _unsavedChangesSrv.constructor(dash, scope);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('No changes should not have changes', function() {
|
||||||
|
expect(tracker.hasChanges()).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -17,6 +17,7 @@ function (angular, $, config) {
|
|||||||
templateValuesSrv,
|
templateValuesSrv,
|
||||||
dynamicDashboardSrv,
|
dynamicDashboardSrv,
|
||||||
dashboardSrv,
|
dashboardSrv,
|
||||||
|
unsavedChangesSrv,
|
||||||
dashboardViewStateSrv,
|
dashboardViewStateSrv,
|
||||||
contextSrv,
|
contextSrv,
|
||||||
$timeout) {
|
$timeout) {
|
||||||
@@ -48,6 +49,7 @@ function (angular, $, config) {
|
|||||||
// the rest of the dashboard can load
|
// the rest of the dashboard can load
|
||||||
templateValuesSrv.init(dashboard).finally(function() {
|
templateValuesSrv.init(dashboard).finally(function() {
|
||||||
dynamicDashboardSrv.init(dashboard);
|
dynamicDashboardSrv.init(dashboard);
|
||||||
|
unsavedChangesSrv.init(dashboard, $scope);
|
||||||
|
|
||||||
$scope.dashboard = dashboard;
|
$scope.dashboard = dashboard;
|
||||||
$scope.dashboardMeta = dashboard.meta;
|
$scope.dashboardMeta = dashboard.meta;
|
||||||
|
|||||||
@@ -1,90 +1,68 @@
|
|||||||
define([
|
define([
|
||||||
'angular',
|
'angular',
|
||||||
'lodash',
|
'lodash',
|
||||||
'config',
|
|
||||||
],
|
],
|
||||||
function(angular, _, config) {
|
function(angular, _) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
if (!config.unsaved_changes_warning) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var module = angular.module('grafana.services');
|
var module = angular.module('grafana.services');
|
||||||
|
|
||||||
module.service('unsavedChangesSrv', function($rootScope, $modal, $q, $location, $timeout, contextSrv) {
|
module.service('unsavedChangesSrv', function($modal, $q, $location, $timeout, contextSrv, $window) {
|
||||||
|
|
||||||
var self = this;
|
function Tracker(dashboard, scope) {
|
||||||
var modalScope = $rootScope.$new();
|
var self = this;
|
||||||
|
|
||||||
$rootScope.$on("dashboard-loaded", function(event, newDashboard) {
|
this.original = dashboard.getSaveModelClone();
|
||||||
// wait for different services to patch the dashboard (missing properties)
|
this.current = dashboard;
|
||||||
$timeout(function() {
|
this.originalPath = $location.path();
|
||||||
self.original = newDashboard.getSaveModelClone();
|
this.scope = scope;
|
||||||
self.current = newDashboard;
|
|
||||||
}, 1200);
|
|
||||||
});
|
|
||||||
|
|
||||||
$rootScope.$on("dashboard-saved", function(event, savedDashboard) {
|
// register events
|
||||||
self.original = savedDashboard.getSaveModelClone();
|
scope.onAppEvent('dashboard-saved', function() {
|
||||||
self.current = savedDashboard;
|
self.original = self.current.getSaveModelClone();
|
||||||
self.orignalPath = $location.path();
|
self.originalPath = $location.path();
|
||||||
});
|
});
|
||||||
|
|
||||||
$rootScope.$on("$routeChangeSuccess", function() {
|
$window.onbeforeunload = function() {
|
||||||
self.original = null;
|
if (self.ignoreChanges()) { return; }
|
||||||
self.originalPath = $location.path();
|
if (self.hasChanges()) {
|
||||||
});
|
return "There are unsaved changes to this dashboard";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.ignoreChanges = function() {
|
scope.$on("$locationChangeStart", function(event, next) {
|
||||||
if (!contextSrv.isEditor) { return true; }
|
|
||||||
if (!self.current || !self.current.meta) { return true; }
|
|
||||||
|
|
||||||
var meta = self.current.meta;
|
|
||||||
return !meta.canSave || meta.fromScript || meta.fromFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onbeforeunload = function() {
|
|
||||||
if (self.ignoreChanges()) { return; }
|
|
||||||
if (self.has_unsaved_changes()) {
|
|
||||||
return "There are unsaved changes to this dashboard";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.init = function() {
|
|
||||||
$rootScope.$on("$locationChangeStart", function(event, next) {
|
|
||||||
// check if we should look for changes
|
// check if we should look for changes
|
||||||
if (self.originalPath === $location.path()) { return true; }
|
if (self.originalPath === $location.path()) { return true; }
|
||||||
if (self.ignoreChanges()) { return true; }
|
if (self.ignoreChanges()) { return true; }
|
||||||
|
|
||||||
if (self.has_unsaved_changes()) {
|
if (self.hasChanges()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
self.next = next;
|
self.next = next;
|
||||||
|
|
||||||
$timeout(self.open_modal);
|
$timeout(function() {
|
||||||
|
self.open_modal();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = Tracker.prototype;
|
||||||
|
|
||||||
|
// for some dashboards and users
|
||||||
|
// changes should be ignored
|
||||||
|
p.ignoreChanges = function() {
|
||||||
|
if (!this.original) { return false; }
|
||||||
|
if (!contextSrv.isEditor) { return true; }
|
||||||
|
if (!this.current || !this.current.meta) { return true; }
|
||||||
|
|
||||||
|
var meta = this.current.meta;
|
||||||
|
return !meta.canSave || meta.fromScript || meta.fromFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.open_modal = function() {
|
// remove stuff that should not count in diff
|
||||||
var confirmModal = $modal({
|
p.cleanDashboardFromIgnoredChanges = function(dash) {
|
||||||
template: './app/partials/unsaved-changes.html',
|
|
||||||
modalClass: 'confirm-modal',
|
|
||||||
persist: true,
|
|
||||||
show: false,
|
|
||||||
scope: modalScope,
|
|
||||||
keyboard: false
|
|
||||||
});
|
|
||||||
|
|
||||||
$q.when(confirmModal).then(function(modalEl) {
|
|
||||||
modalEl.modal('show');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.cleanDashboardFromRepeatedPanelsAndRows = function(dash) {
|
|
||||||
dash.rows = _.filter(dash.rows, function(row) {
|
dash.rows = _.filter(dash.rows, function(row) {
|
||||||
if (row.repeatRowId) {
|
if (row.repeatRowId) {
|
||||||
console.log('filtering out row');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,13 +79,9 @@ function(angular, _, config) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.has_unsaved_changes = function() {
|
p.hasChanges = function() {
|
||||||
if (!self.original) {
|
var current = this.current.getSaveModelClone();
|
||||||
return false;
|
var original = this.original;
|
||||||
}
|
|
||||||
|
|
||||||
var current = self.current.getSaveModelClone();
|
|
||||||
var original = self.original;
|
|
||||||
|
|
||||||
// ignore timespan changes
|
// ignore timespan changes
|
||||||
current.time = original.time = {};
|
current.time = original.time = {};
|
||||||
@@ -126,8 +100,8 @@ function(angular, _, config) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cleanDashboardFromRepeatedPanelsAndRows(current);
|
this.cleanDashboardFromIgnoredChanges(current);
|
||||||
this.cleanDashboardFromRepeatedPanelsAndRows(original);
|
this.cleanDashboardFromIgnoredChanges(original);
|
||||||
|
|
||||||
// ignore some panel and row stuff
|
// ignore some panel and row stuff
|
||||||
current.forEachPanel(function(panel, panelIndex, row, rowIndex) {
|
current.forEachPanel(function(panel, panelIndex, row, rowIndex) {
|
||||||
@@ -165,28 +139,43 @@ function(angular, _, config) {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.goto_next = function() {
|
p.open_modal = function() {
|
||||||
|
var tracker = this;
|
||||||
|
|
||||||
|
var modalScope = this.scope.$new();
|
||||||
|
modalScope.ignore = function() {
|
||||||
|
tracker.original = null;
|
||||||
|
tracker.goto_next();
|
||||||
|
};
|
||||||
|
|
||||||
|
modalScope.save = function() {
|
||||||
|
tracker.scope.$emit('save-dashboard');
|
||||||
|
};
|
||||||
|
|
||||||
|
var confirmModal = $modal({
|
||||||
|
template: './app/partials/unsaved-changes.html',
|
||||||
|
modalClass: 'confirm-modal',
|
||||||
|
persist: false,
|
||||||
|
show: false,
|
||||||
|
scope: modalScope,
|
||||||
|
keyboard: false
|
||||||
|
});
|
||||||
|
|
||||||
|
$q.when(confirmModal).then(function(modalEl) {
|
||||||
|
modalEl.modal('show');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
p.goto_next = function() {
|
||||||
var baseLen = $location.absUrl().length - $location.url().length;
|
var baseLen = $location.absUrl().length - $location.url().length;
|
||||||
var nextUrl = self.next.substring(baseLen);
|
var nextUrl = this.next.substring(baseLen);
|
||||||
$location.url(nextUrl);
|
$location.url(nextUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
modalScope.ignore = function() {
|
this.Tracker = Tracker;
|
||||||
self.original = null;
|
this.init = function(dashboard, scope) {
|
||||||
self.goto_next();
|
// wait for different services to patch the dashboard (missing properties)
|
||||||
|
$timeout(function() { new Tracker(dashboard, scope); }, 1200);
|
||||||
};
|
};
|
||||||
|
|
||||||
modalScope.save = function() {
|
|
||||||
var unregister = $rootScope.$on('dashboard-saved', function() {
|
|
||||||
self.goto_next();
|
|
||||||
});
|
|
||||||
|
|
||||||
$timeout(unregister, 2000);
|
|
||||||
|
|
||||||
$rootScope.$emit('save-dashboard');
|
|
||||||
};
|
|
||||||
|
|
||||||
}).run(function(unsavedChangesSrv) {
|
|
||||||
unsavedChangesSrv.init();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
48
public/test/specs/unsavedChangesSrv-specs.js
Normal file
48
public/test/specs/unsavedChangesSrv-specs.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
define([
|
||||||
|
'features/dashboard/unsavedChangesSrv',
|
||||||
|
'features/dashboard/dashboardSrv'
|
||||||
|
], function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("unsavedChangesSrv", function() {
|
||||||
|
var _unsavedChangesSrv;
|
||||||
|
var _dashboardSrv;
|
||||||
|
var _location;
|
||||||
|
var _contextSrvStub = { isEditor: true };
|
||||||
|
var _rootScope;
|
||||||
|
var tracker;
|
||||||
|
var dash;
|
||||||
|
var scope;
|
||||||
|
|
||||||
|
beforeEach(module('grafana.services'));
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('contextSrv', _contextSrvStub);
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(inject(function(unsavedChangesSrv, $location, $rootScope, dashboardSrv) {
|
||||||
|
_unsavedChangesSrv = unsavedChangesSrv;
|
||||||
|
_dashboardSrv = dashboardSrv;
|
||||||
|
_location = $location;
|
||||||
|
_rootScope = $rootScope;
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
dash = _dashboardSrv.create({});
|
||||||
|
scope = _rootScope.$new();
|
||||||
|
scope.appEvent = sinon.spy();
|
||||||
|
scope.onAppEvent = sinon.spy();
|
||||||
|
|
||||||
|
tracker = new _unsavedChangesSrv.Tracker(dash, scope);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('No changes should not have changes', function() {
|
||||||
|
expect(tracker.hasChanges()).to.be(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Simple change should be registered', function() {
|
||||||
|
dash.property = "google";
|
||||||
|
expect(tracker.hasChanges()).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -141,6 +141,7 @@ require([
|
|||||||
'specs/dashboardViewStateSrv-specs',
|
'specs/dashboardViewStateSrv-specs',
|
||||||
'specs/soloPanelCtrl-specs',
|
'specs/soloPanelCtrl-specs',
|
||||||
'specs/dynamicDashboardSrv-specs',
|
'specs/dynamicDashboardSrv-specs',
|
||||||
|
'specs/unsavedChangesSrv-specs',
|
||||||
];
|
];
|
||||||
|
|
||||||
var pluginSpecs = (config.plugins.specs || []).map(function (spec) {
|
var pluginSpecs = (config.plugins.specs || []).map(function (spec) {
|
||||||
|
|||||||
Reference in New Issue
Block a user