mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Merge pull request #215 from ccowan/state-refactor
State Object Refactor
This commit is contained in:
commit
8796a11176
22 changed files with 681 additions and 105 deletions
|
@ -40,7 +40,7 @@ define(function (require) {
|
|||
}
|
||||
});
|
||||
|
||||
app.directive('dashboardApp', function (Notifier, courier, savedVisualizations, AppState, timefilter) {
|
||||
app.directive('dashboardApp', function (Notifier, courier, savedVisualizations, appStateFactory, timefilter) {
|
||||
return {
|
||||
controller: function ($scope, $route, $routeParams, $location, configFile) {
|
||||
var notify = new Notifier({
|
||||
|
@ -56,7 +56,7 @@ define(function (require) {
|
|||
query: ''
|
||||
};
|
||||
|
||||
var $state = $scope.state = new AppState(stateDefaults);
|
||||
var $state = $scope.state = appStateFactory.create(stateDefaults);
|
||||
|
||||
$scope.configTemplate = new ConfigTemplate({
|
||||
save: require('text!apps/dashboard/partials/save_dashboard.html'),
|
||||
|
@ -94,13 +94,13 @@ define(function (require) {
|
|||
|
||||
$scope.filterResults = function () {
|
||||
updateQueryOnRootSource();
|
||||
$state.commit();
|
||||
$state.save();
|
||||
courier.fetch();
|
||||
};
|
||||
|
||||
$scope.save = function () {
|
||||
$state.title = dash.id = dash.title;
|
||||
$state.commit();
|
||||
$state.save();
|
||||
dash.panelsJSON = JSON.stringify($state.panels);
|
||||
|
||||
dash.save()
|
||||
|
@ -117,7 +117,7 @@ define(function (require) {
|
|||
$scope.$on('ready:vis', function () {
|
||||
if (pendingVis) pendingVis--;
|
||||
if (pendingVis === 0) {
|
||||
$state.commit();
|
||||
$state.save();
|
||||
courier.fetch();
|
||||
}
|
||||
});
|
||||
|
@ -125,7 +125,7 @@ define(function (require) {
|
|||
// listen for notifications from the grid component that changes have
|
||||
// been made, rather than watching the panels deeply
|
||||
$scope.$on('change:vis', function () {
|
||||
$state.commit();
|
||||
$state.save();
|
||||
});
|
||||
|
||||
// called by the saved-object-finder when a user clicks a vis
|
||||
|
|
|
@ -49,7 +49,7 @@ define(function (require) {
|
|||
|
||||
|
||||
app.controller('discover', function ($scope, config, courier, $route, $window, savedSearches, savedVisualizations,
|
||||
Notifier, $location, globalState, AppState, timefilter, AdhocVis, Promise, Private) {
|
||||
Notifier, $location, globalState, appStateFactory, timefilter, AdhocVis, Promise, Private) {
|
||||
|
||||
var segmentedFetch = $scope.segmentedFetch = Private(require('apps/discover/_segmented_fetch'));
|
||||
var HitSortFn = Private(require('apps/discover/_hit_sort_fn'));
|
||||
|
@ -93,7 +93,7 @@ define(function (require) {
|
|||
'year'
|
||||
];
|
||||
|
||||
var $state = $scope.state = new AppState(stateDefaults);
|
||||
var $state = $scope.state = new appStateFactory.create(stateDefaults);
|
||||
|
||||
if (!_.contains(indexPatternList, $state.index)) {
|
||||
var reason = 'The index specified in the URL is not a configured pattern. ';
|
||||
|
@ -202,7 +202,7 @@ define(function (require) {
|
|||
$scope.updateDataSource()
|
||||
.then(setupVisualization)
|
||||
.then(function () {
|
||||
$state.commit();
|
||||
$state.save();
|
||||
|
||||
var sort = $state.sort;
|
||||
var timeField = $scope.searchSource.get('index').timeFieldName;
|
||||
|
@ -331,9 +331,7 @@ define(function (require) {
|
|||
};
|
||||
|
||||
$scope.resetQuery = function () {
|
||||
$state.query = stateDefaults.query;
|
||||
$state.sort = stateDefaults.sort;
|
||||
$state.columns = stateDefaults.columns;
|
||||
$state.reset();
|
||||
$scope.fetch();
|
||||
};
|
||||
|
||||
|
@ -489,7 +487,7 @@ define(function (require) {
|
|||
}
|
||||
|
||||
// if this commit results in something besides the columns changing, a fetch will be executed.
|
||||
$state.commit();
|
||||
$state.save();
|
||||
}
|
||||
|
||||
// TODO: Move to utility class
|
||||
|
@ -554,7 +552,7 @@ define(function (require) {
|
|||
configs: [{
|
||||
agg: 'date_histogram',
|
||||
field: $scope.opts.timefield,
|
||||
interval: $scope.state.interval,
|
||||
interval: $state.interval,
|
||||
min_doc_count: 0,
|
||||
}]
|
||||
},
|
||||
|
@ -585,4 +583,4 @@ define(function (require) {
|
|||
|
||||
init();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -127,7 +127,7 @@ define(function (require) {
|
|||
group: [],
|
||||
split: [],
|
||||
}),
|
||||
_g: rison.encode(globalState)
|
||||
_g: globalState.toRISON()
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -230,4 +230,4 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,9 +12,9 @@ define(function (require) {
|
|||
.directive('kbnSettingsObjects', function (config, Notifier, Private) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: function ($scope, $injector, $q, AppState) {
|
||||
controller: function ($scope, $injector, $q, appStateFactory) {
|
||||
|
||||
var $state = $scope.state = new AppState();
|
||||
var $state = $scope.state = new appStateFactory.create();
|
||||
|
||||
var resetCheckBoxes = function () {
|
||||
$scope.deleteAll = false;
|
||||
|
@ -71,7 +71,7 @@ define(function (require) {
|
|||
|
||||
$scope.changeTab = function (obj) {
|
||||
$state.tab = obj.title;
|
||||
$state.commit();
|
||||
$state.save();
|
||||
resetCheckBoxes();
|
||||
};
|
||||
|
||||
|
@ -82,4 +82,4 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -47,7 +47,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
app.controller('VisualizeEditor', function ($scope, $route, $timeout, $window, Notifier, $location,
|
||||
globalState, AppState, timefilter, Private) {
|
||||
globalState, appStateFactory, timefilter, Private) {
|
||||
var aggs = Private(require('apps/visualize/saved_visualizations/_aggs'));
|
||||
|
||||
var notify = new Notifier({
|
||||
|
@ -63,7 +63,7 @@ define(function (require) {
|
|||
$scope.fields = _.sortBy(indexPattern.fields, 'name');
|
||||
$scope.fields.byName = indexPattern.fieldsByName;
|
||||
|
||||
var $state = $scope.state = new AppState(vis.getState());
|
||||
var $state = $scope.state = new appStateFactory.create(vis.getState());
|
||||
|
||||
if ($state.query) {
|
||||
vis.searchSource.set('query', $state.query);
|
||||
|
@ -107,7 +107,7 @@ define(function (require) {
|
|||
var writeStateAndFetch = function () {
|
||||
_.assign($state, vis.getState());
|
||||
watchForConfigChanges();
|
||||
$state.commit();
|
||||
$state.save();
|
||||
justFetch();
|
||||
};
|
||||
|
||||
|
@ -256,4 +256,4 @@ define(function (require) {
|
|||
init();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,24 +1,15 @@
|
|||
define(function (require) {
|
||||
var module = require('modules').get('kibana/factories');
|
||||
var _ = require('lodash');
|
||||
|
||||
require('components/state_management/global_state');
|
||||
|
||||
module.factory('AppState', function (globalState, $route, $location, Promise) {
|
||||
return function AppStateProvider(Private) {
|
||||
var State = Private(require('components/state_management/state'));
|
||||
|
||||
_.inherits(AppState, State);
|
||||
function AppState(defaults) {
|
||||
globalState._setApp(this, defaults);
|
||||
|
||||
this.onUpdate = function (handler) {
|
||||
return globalState.onAppUpdate(handler);
|
||||
};
|
||||
|
||||
this.commit = function () {
|
||||
var diff = globalState.commit();
|
||||
return diff.app.all;
|
||||
};
|
||||
AppState.Super.call(this, '_a', defaults);
|
||||
}
|
||||
|
||||
return AppState;
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
|
|
41
src/kibana/components/state_management/app_state_factory.js
Normal file
41
src/kibana/components/state_management/app_state_factory.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var module = require('modules').get('kibana/factories');
|
||||
|
||||
module.service('appStateFactory', function (Private) {
|
||||
var AppState = Private(require('components/state_management/app_state'));
|
||||
|
||||
/**
|
||||
* We need to keep reference to the current so we can destroy it. When
|
||||
* we create a new one. The needs to happen because we need to reset the
|
||||
* listner queue and remove the refercne to the RootScope.
|
||||
*/
|
||||
var currentAppState;
|
||||
|
||||
var appStateFactory = {
|
||||
|
||||
/**
|
||||
* Factory method for creating AppStates
|
||||
* @param {string} appName The app name
|
||||
* @param {object} defaults The the defaults for the AppState
|
||||
* @returns {object} AppState
|
||||
*/
|
||||
create: function (defaults) {
|
||||
|
||||
// Check to see if the state already exists. If it does then we need
|
||||
// to call the destory method.
|
||||
if (!_.isUndefined(currentAppState)) {
|
||||
currentAppState.destroy();
|
||||
}
|
||||
currentAppState = new AppState(defaults);
|
||||
|
||||
return currentAppState;
|
||||
}
|
||||
};
|
||||
|
||||
return appStateFactory;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -6,61 +6,18 @@ define(function (require) {
|
|||
|
||||
var module = require('modules').get('kibana/global_state');
|
||||
|
||||
module.service('globalState', function (Private, $rootScope, $route, $injector, Promise, PromiseEmitter) {
|
||||
var globalState = this;
|
||||
module.service('globalState', function (Private, $rootScope) {
|
||||
var State = Private(require('components/state_management/state'));
|
||||
|
||||
var setupSync = Private(require('components/state_management/_state_sync'));
|
||||
_.inherits(GlobalState, State);
|
||||
function GlobalState(defaults) {
|
||||
GlobalState.Super.call(this, '_g', defaults);
|
||||
}
|
||||
|
||||
// store app related stuff in here
|
||||
var app = {};
|
||||
|
||||
// resolve all of these when a global update is detected coming in from the url
|
||||
var updateListeners = [];
|
||||
|
||||
// resolve all of these when ANY global update is detected coming in from the url
|
||||
var anyUpdateListeners = [];
|
||||
|
||||
globalState._setApp = function (newAppState, defaults) {
|
||||
app.current = newAppState;
|
||||
app.defaults = _.cloneDeep(defaults);
|
||||
app.name = $route.current.$$route.originalPath;
|
||||
app.listeners = [];
|
||||
|
||||
sync.pull();
|
||||
GlobalState.prototype.writeToUrl = function (url) {
|
||||
return qs.replaceParamInUrl(url, this._urlParam, this.toRISON());
|
||||
};
|
||||
|
||||
globalState.writeToUrl = function (url) {
|
||||
return qs.replaceParamInUrl(url, '_g', rison.encode(globalState));
|
||||
};
|
||||
|
||||
// exposes sync.pull and sync.push
|
||||
var sync = setupSync(globalState, updateListeners, app);
|
||||
|
||||
$rootScope.$on('$routeUpdate', function () {
|
||||
sync.pull();
|
||||
});
|
||||
|
||||
globalState.onUpdate = function (handler) {
|
||||
return new PromiseEmitter(function (resolve, reject, defer) {
|
||||
updateListeners.push(defer);
|
||||
}, handler);
|
||||
};
|
||||
|
||||
globalState.onAppUpdate = function (handler) {
|
||||
return new PromiseEmitter(function (resolve, reject, defer) {
|
||||
app.listeners.push(defer);
|
||||
}, handler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Commit the globalState as a history item
|
||||
*/
|
||||
globalState.commit = function () {
|
||||
return sync.push(true);
|
||||
};
|
||||
|
||||
// pull in the default globalState
|
||||
sync.pull();
|
||||
return new GlobalState();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
92
src/kibana/components/state_management/state.js
Normal file
92
src/kibana/components/state_management/state.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var rison = require('utils/rison');
|
||||
|
||||
var applyDiff = require('utils/diff_object');
|
||||
|
||||
return function StateProvider(Private, $rootScope, $location) {
|
||||
var Events = Private(require('factories/_events'));
|
||||
|
||||
_.inherits(State, Events);
|
||||
function State(urlParam, defaults) {
|
||||
State.Super.call(this);
|
||||
this._defaults = defaults || {};
|
||||
this._urlParam = urlParam || '_s';
|
||||
|
||||
// When the URL updates we need to fetch the values from the URL
|
||||
this._deregisterRouteUpdate = $rootScope.$on('$routeUpdate', _.bindKey(this, 'fetch'));
|
||||
|
||||
// Initialize the State with fetch
|
||||
this.fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the state from the url
|
||||
* @returns {void}
|
||||
*/
|
||||
State.prototype.fetch = function () {
|
||||
var search = $location.search();
|
||||
var stash = rison.decode(search[this._urlParam] || '()');
|
||||
_.defaults(stash, this._defaults);
|
||||
// apply diff to state from stash, this is side effecting so
|
||||
// it will change state in place.
|
||||
var diffResults = applyDiff(this, stash);
|
||||
if (diffResults.keys.length) {
|
||||
this.emit('fetch_with_changes', diffResults.keys);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves the state to the url
|
||||
* @returns {void}
|
||||
*/
|
||||
State.prototype.save = function () {
|
||||
var search = $location.search();
|
||||
var stash = rison.decode(search[this._urlParam] || '()');
|
||||
var state = this.toObject();
|
||||
_.defaults(state, this._defaults);
|
||||
// apply diff to stash from state, this is side effecting so
|
||||
// it will change stash in place.
|
||||
var diffResults = applyDiff(stash, state);
|
||||
if (diffResults.keys.length) {
|
||||
this.emit('save_with_changes', diffResults.keys);
|
||||
}
|
||||
search[this._urlParam] = this.toRISON();
|
||||
$location.search(search);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets the state to the defaults
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
State.prototype.reset = function () {
|
||||
// apply diff to _attributes from defaults, this is side effecting so
|
||||
// it will change the state in place.
|
||||
applyDiff(this, this._defaults);
|
||||
this.save();
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a listner for updates to pulls
|
||||
* TODO: Change all the references to onUpdate to the actual fetch_with_changes event
|
||||
* @returns {void}
|
||||
*/
|
||||
State.prototype.onUpdate = function (cb) {
|
||||
this.on('fetch_with_changes', cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleans up the state object
|
||||
* @returns {void}
|
||||
*/
|
||||
State.prototype.destroy = function () {
|
||||
this.off(); // removes all listners
|
||||
this._deregisterRouteUpdate(); // Removes the $routeUpdate listner
|
||||
};
|
||||
|
||||
return State;
|
||||
|
||||
};
|
||||
|
||||
});
|
|
@ -9,6 +9,7 @@ define(function (require) {
|
|||
require('components/config/config');
|
||||
require('components/courier/courier');
|
||||
require('components/notify/notify');
|
||||
require('components/state_management/app_state_factory');
|
||||
require('directives/info');
|
||||
require('directives/spinner');
|
||||
require('directives/paginate');
|
||||
|
@ -112,7 +113,7 @@ define(function (require) {
|
|||
|
||||
var writeGlobalStateToLastPaths = function () {
|
||||
var currentUrl = $location.url();
|
||||
var _g = rison.encode(globalState);
|
||||
var _g = globalState.toRISON();
|
||||
|
||||
$scope.apps.forEach(function (app) {
|
||||
var url = lastPathFor(app);
|
||||
|
@ -124,8 +125,7 @@ define(function (require) {
|
|||
|
||||
var writeTime = function (newVal, oldVal) {
|
||||
globalState.time = _.clone(timefilter.time);
|
||||
|
||||
globalState.commit();
|
||||
globalState.save();
|
||||
writeGlobalStateToLastPaths();
|
||||
};
|
||||
|
||||
|
|
43
src/kibana/factories/_base_object.js
Normal file
43
src/kibana/factories/_base_object.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var rison = require('utils/rison');
|
||||
|
||||
return function BaseObjectProvider() {
|
||||
|
||||
function BaseObject(attributes) {
|
||||
// Set the attributes or default to an empty object
|
||||
_.assign(this, attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attirbutes for the objct
|
||||
* @returns {object}
|
||||
*/
|
||||
BaseObject.prototype.toObject = function () {
|
||||
// return just the data.
|
||||
return _.omit(this, function (value, key) {
|
||||
return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the model to RISON
|
||||
* @returns {string}
|
||||
*/
|
||||
BaseObject.prototype.toRISON = function () {
|
||||
return rison.encode(this.toObject());
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the model to JSON
|
||||
* @returns {object}
|
||||
*/
|
||||
BaseObject.prototype.toJSON = function () {
|
||||
return this.toObject();
|
||||
};
|
||||
|
||||
return BaseObject;
|
||||
|
||||
|
||||
};
|
||||
});
|
77
src/kibana/factories/_events.js
Normal file
77
src/kibana/factories/_events.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
return function EventsProvider(Private, PromiseEmitter) {
|
||||
var BaseObject = Private(require('factories/_base_object'));
|
||||
|
||||
_.inherits(Events, BaseObject);
|
||||
function Events() {
|
||||
Events.Super.call(this);
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for events
|
||||
* @param {string} name The name of the event
|
||||
* @param {function} handler The handler for the event
|
||||
* @returns {PromiseEmitter}
|
||||
*/
|
||||
Events.prototype.on = function (name, handler) {
|
||||
var self = this;
|
||||
|
||||
if (!_.isArray(this._listeners[name])) {
|
||||
this._listeners[name] = [];
|
||||
}
|
||||
|
||||
return new PromiseEmitter(function (resolve, reject, defer) {
|
||||
defer._handler = handler;
|
||||
self._listeners[name].push(defer);
|
||||
}, handler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a event listner
|
||||
* @param {string} name The name of the event
|
||||
* @param {function} [handler] The handler to remove
|
||||
* @return {void}
|
||||
*/
|
||||
Events.prototype.off = function (name, handler) {
|
||||
if (!name && !handler) {
|
||||
return this._listeners = {};
|
||||
}
|
||||
|
||||
// exit early if there is not an event that matches
|
||||
if (!this._listeners[name]) return;
|
||||
|
||||
// If no hander remove all the events
|
||||
if (!handler) {
|
||||
delete this._listeners[name];
|
||||
} else {
|
||||
this._listeners[name] = _.filter(this._listeners[name], function (defer) {
|
||||
return handler !== defer._handler;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits and event using the PromiseEmitter
|
||||
* @param {string} name The name of the event
|
||||
* @param {mixed} args The args to pass along to the handers
|
||||
* @returns {void}
|
||||
*/
|
||||
Events.prototype.emit = function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var name = args.shift();
|
||||
if (this._listeners[name]) {
|
||||
// We need to empty the array when we resolve the listners. PromiseEmitter
|
||||
// will regenerate the listners array with new promises.
|
||||
_.each(this._listeners[name].splice(0), function (defer) {
|
||||
defer.resolve.apply(defer, args);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return Events;
|
||||
|
||||
};
|
||||
});
|
|
@ -56,4 +56,4 @@ define(function (require) {
|
|||
|
||||
return Private;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ define(function (require) {
|
|||
var moment = require('moment');
|
||||
var datemath = require('utils/datemath');
|
||||
var module = require('modules').get('kibana');
|
||||
require('components/state_management/global_state');
|
||||
|
||||
module.service('timefilter', function (Promise, globalState, $rootScope) {
|
||||
|
||||
|
@ -33,8 +34,9 @@ define(function (require) {
|
|||
};
|
||||
|
||||
var castTime = function () {
|
||||
if (globalState.time && globalState.time.from) self.time.from = convertISO8601(globalState.time.from);
|
||||
if (globalState.time && globalState.time.to) self.time.to = convertISO8601(globalState.time.to);
|
||||
var time = globalState.time;
|
||||
if (time && time.from) self.time.from = convertISO8601(time.from);
|
||||
if (time && time.to) self.time.to = convertISO8601(time.to);
|
||||
};
|
||||
|
||||
this.enabled = function (state) {
|
||||
|
@ -68,4 +70,4 @@ define(function (require) {
|
|||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
58
src/kibana/utils/diff_object.js
Normal file
58
src/kibana/utils/diff_object.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var angular = require('angular');
|
||||
|
||||
return function (target, source) {
|
||||
|
||||
var diff = {};
|
||||
|
||||
/**
|
||||
* Filter the private vars
|
||||
* @param {string} key The keys
|
||||
* @returns {boolean}
|
||||
*/
|
||||
var filterPrivateAndMethods = function (obj) {
|
||||
return function (key) {
|
||||
if (_.isFunction(obj[key])) return false;
|
||||
if (key.charAt(0) === '$') return false;
|
||||
return key.charAt(0) !== '_';
|
||||
};
|
||||
};
|
||||
|
||||
var targetKeys = _(target)
|
||||
.keys()
|
||||
.filter(filterPrivateAndMethods(target))
|
||||
.value();
|
||||
|
||||
var sourceKeys = _(source)
|
||||
.keys()
|
||||
.filter(filterPrivateAndMethods(source))
|
||||
.value();
|
||||
|
||||
|
||||
// Find the keys to be removed
|
||||
diff.removed = _.difference(targetKeys, sourceKeys);
|
||||
|
||||
// Find the keys to be added
|
||||
diff.added = _.difference(sourceKeys, targetKeys);
|
||||
|
||||
// Find the keys that will be changed
|
||||
diff.changed = _.filter(sourceKeys, function (key) {
|
||||
return !angular.equals(target[key]);
|
||||
});
|
||||
|
||||
// Make a list of all the keys that are changing
|
||||
diff.keys = _.union(diff.changed, diff.removed, diff.added);
|
||||
|
||||
// Remove all the keys
|
||||
_.each(diff.removed, function (key) {
|
||||
delete target[key];
|
||||
});
|
||||
|
||||
// Assign the source to the target
|
||||
_.assign(target, _.pick(source, sourceKeys));
|
||||
|
||||
return diff;
|
||||
|
||||
};
|
||||
});
|
|
@ -495,4 +495,4 @@ define(function () {
|
|||
};
|
||||
/* jshint ignore:end */
|
||||
return rison;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -73,7 +73,11 @@
|
|||
'specs/utils/interval',
|
||||
'specs/utils/versionmath',
|
||||
'specs/utils/routes/index',
|
||||
'specs/courier/search_source/_get_normalized_sort'
|
||||
'specs/courier/search_source/_get_normalized_sort',
|
||||
'specs/factories/_base_object',
|
||||
'specs/state_management/state',
|
||||
'specs/utils/diff_object',
|
||||
'specs/factories/_events'
|
||||
], function (kibana, sinon) {
|
||||
kibana.load(function () {
|
||||
var xhr = sinon.useFakeXMLHttpRequest();
|
||||
|
@ -95,4 +99,4 @@
|
|||
})();</script>
|
||||
</head>
|
||||
<body><div id="mocha"></div></body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -326,4 +326,4 @@ define(function (require) {
|
|||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
48
test/unit/specs/factories/_base_object.js
Normal file
48
test/unit/specs/factories/_base_object.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('sinon/sinon');
|
||||
require('services/private');
|
||||
|
||||
// Load kibana
|
||||
require('index');
|
||||
|
||||
describe('State Management', function () {
|
||||
describe('BaseObject', function () {
|
||||
var $rootScope;
|
||||
var BaseObject;
|
||||
|
||||
beforeEach(function () {
|
||||
module('kibana');
|
||||
|
||||
inject(function (_$rootScope_, Private) {
|
||||
$rootScope = _$rootScope_;
|
||||
BaseObject = Private(require('factories/_base_object'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should take an inital set of values', function () {
|
||||
var baseObject = new BaseObject({ message: 'test' });
|
||||
expect(baseObject).to.have.property('message', 'test');
|
||||
});
|
||||
|
||||
it('should serialize _attributes to RISON', function () {
|
||||
var baseObject = new BaseObject();
|
||||
baseObject.message = 'Testing... 1234';
|
||||
var rison = baseObject.toRISON();
|
||||
expect(rison).to.equal('(message:\'Testing... 1234\')');
|
||||
});
|
||||
|
||||
it('should serialize _attributes for JSON', function () {
|
||||
var baseObject = new BaseObject();
|
||||
baseObject.message = 'Testing... 1234';
|
||||
baseObject._private = 'foo';
|
||||
baseObject.$private = 'stuff';
|
||||
var json = JSON.stringify(baseObject);
|
||||
expect(json).to.equal('{"message":"Testing... 1234"}');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
86
test/unit/specs/factories/_events.js
Normal file
86
test/unit/specs/factories/_events.js
Normal file
|
@ -0,0 +1,86 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('sinon/sinon');
|
||||
require('services/private');
|
||||
|
||||
// Load kibana
|
||||
require('index');
|
||||
|
||||
describe('State Management', function () {
|
||||
describe('Events', function () {
|
||||
var $rootScope;
|
||||
var Events;
|
||||
|
||||
beforeEach(function () {
|
||||
module('kibana');
|
||||
|
||||
inject(function (_$rootScope_, Private) {
|
||||
$rootScope = _$rootScope_;
|
||||
Events = Private(require('factories/_events'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle on events', function (done) {
|
||||
var obj = new Events();
|
||||
obj.on('test', function (message) {
|
||||
expect(message).to.equal('Hello World');
|
||||
done();
|
||||
});
|
||||
obj.emit('test', 'Hello World');
|
||||
$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should work with inherited objects', function (done) {
|
||||
_.inherits(MyEventedObject, Events);
|
||||
function MyEventedObject() {
|
||||
MyEventedObject.Super.call(this);
|
||||
}
|
||||
var obj = new MyEventedObject();
|
||||
obj.on('test', function (message) {
|
||||
expect(message).to.equal('Hello World');
|
||||
done();
|
||||
});
|
||||
obj.emit('test', 'Hello World');
|
||||
$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should clear events when off is called', function () {
|
||||
var obj = new Events();
|
||||
obj.on('test', _.noop);
|
||||
expect(obj._listeners).to.have.property('test');
|
||||
expect(obj._listeners['test']).to.have.length(1);
|
||||
obj.off();
|
||||
expect(obj._listeners).to.not.have.property('test');
|
||||
});
|
||||
|
||||
it('should clear a specific handler when off is called for an event', function () {
|
||||
var obj = new Events();
|
||||
var handler1 = sinon.stub();
|
||||
var handler2 = sinon.stub();
|
||||
obj.on('test', handler1);
|
||||
obj.on('test', handler2);
|
||||
expect(obj._listeners).to.have.property('test');
|
||||
obj.off('test', handler1);
|
||||
obj.emit('test', 'Hello World');
|
||||
$rootScope.$apply();
|
||||
sinon.assert.calledOnce(handler2);
|
||||
sinon.assert.notCalled(handler1);
|
||||
});
|
||||
|
||||
it('should clear a all handlers when off is called for an event', function () {
|
||||
var obj = new Events();
|
||||
var handler1 = sinon.stub();
|
||||
obj.on('test', handler1);
|
||||
expect(obj._listeners).to.have.property('test');
|
||||
obj.off('test');
|
||||
expect(obj._listeners).to.not.have.property('test');
|
||||
obj.emit('test', 'Hello World');
|
||||
$rootScope.$apply();
|
||||
sinon.assert.notCalled(handler1);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
118
test/unit/specs/state_management/state.js
Normal file
118
test/unit/specs/state_management/state.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('sinon/sinon');
|
||||
require('services/private');
|
||||
|
||||
// Load kibana
|
||||
require('index');
|
||||
|
||||
describe('State Management', function () {
|
||||
describe('State', function () {
|
||||
|
||||
var $rootScope, $location, State, Events;
|
||||
|
||||
beforeEach(function () {
|
||||
module('kibana');
|
||||
|
||||
inject(function (_$rootScope_, _$location_, Private) {
|
||||
$location = _$location_;
|
||||
$rootScope = _$rootScope_;
|
||||
State = Private(require('components/state_management/state'));
|
||||
Events = Private(require('factories/_events'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should inherit from Events', function () {
|
||||
var state = new State();
|
||||
expect(state).to.be.an(Events);
|
||||
});
|
||||
|
||||
it('should save to $location.search()', function () {
|
||||
var state = new State('_s', { test: 'foo' });
|
||||
state.save();
|
||||
var search = $location.search();
|
||||
expect(search).to.have.property('_s');
|
||||
expect(search._s).to.equal('(test:foo)');
|
||||
});
|
||||
|
||||
it('should emit an event if changes are saved', function (done) {
|
||||
var state = new State();
|
||||
state.on('save_with_changes', function (keys) {
|
||||
expect(keys).to.eql(['test']);
|
||||
done();
|
||||
});
|
||||
state.test = 'foo';
|
||||
state.save();
|
||||
var search = $location.search();
|
||||
$rootScope.$apply();
|
||||
});
|
||||
|
||||
|
||||
it('should emit an event if changes are fetched', function (done) {
|
||||
var state = new State();
|
||||
state.on('fetch_with_changes', function (keys) {
|
||||
expect(keys).to.eql(['foo']);
|
||||
done();
|
||||
});
|
||||
$location.search({ _s: '(foo:bar)' });
|
||||
state.fetch();
|
||||
expect(state).to.have.property('foo', 'bar');
|
||||
$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should have events that attach to scope', function (done) {
|
||||
var state = new State();
|
||||
state.on('test', function (message) {
|
||||
expect(message).to.equal('foo');
|
||||
done();
|
||||
});
|
||||
state.emit('test', 'foo');
|
||||
$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should fire listeners for #onUpdate() on #fetch()', function (done) {
|
||||
var state = new State();
|
||||
state.onUpdate(function (keys) {
|
||||
expect(keys).to.eql(['foo']);
|
||||
done();
|
||||
});
|
||||
$location.search({ _s: '(foo:bar)' });
|
||||
state.fetch();
|
||||
expect(state).to.have.property('foo', 'bar');
|
||||
$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should apply defaults to fetches', function () {
|
||||
var state = new State('_s', { message: 'test' });
|
||||
$location.search({ _s: '(foo:bar)' });
|
||||
state.fetch();
|
||||
expect(state).to.have.property('foo', 'bar');
|
||||
expect(state).to.have.property('message', 'test');
|
||||
});
|
||||
|
||||
it('should reset the state to the defaults', function () {
|
||||
var state = new State('_s', { message: ['test'] });
|
||||
state.reset();
|
||||
var search = $location.search();
|
||||
expect(search).to.have.property('_s');
|
||||
expect(search._s).to.equal('(message:!(test))');
|
||||
expect(state.message).to.eql(['test']);
|
||||
});
|
||||
|
||||
it('should apply the defaults upon initialization', function () {
|
||||
var state = new State('_s', { message: 'test' });
|
||||
expect(state).to.have.property('message', 'test');
|
||||
});
|
||||
|
||||
it('should call fetch when $routeUpdate is fired on $rootScope', function () {
|
||||
var state = new State();
|
||||
var spy = sinon.spy(state, 'fetch');
|
||||
$rootScope.$emit('$routeUpdate', 'test');
|
||||
sinon.assert.calledOnce(spy);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
61
test/unit/specs/utils/diff_object.js
Normal file
61
test/unit/specs/utils/diff_object.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
define(function (require) {
|
||||
var diff = require('utils/diff_object');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('utils/diff_object', function () {
|
||||
|
||||
it('should list the removed keys', function () {
|
||||
var target = { test: 'foo' };
|
||||
var source = { foo: 'test' };
|
||||
var results = diff(target, source);
|
||||
expect(results).to.have.property('removed');
|
||||
expect(results.removed).to.eql(['test']);
|
||||
});
|
||||
|
||||
it('should list the changed keys', function () {
|
||||
var target = { foo: 'bar' };
|
||||
var source = { foo: 'test' };
|
||||
var results = diff(target, source);
|
||||
expect(results).to.have.property('changed');
|
||||
expect(results.changed).to.eql(['foo']);
|
||||
});
|
||||
|
||||
it('should list the added keys', function () {
|
||||
var target = { };
|
||||
var source = { foo: 'test' };
|
||||
var results = diff(target, source);
|
||||
expect(results).to.have.property('added');
|
||||
expect(results.added).to.eql(['foo']);
|
||||
});
|
||||
|
||||
it('should list all the keys that are change or removed', function () {
|
||||
var target = { foo: 'bar', test: 'foo' };
|
||||
var source = { foo: 'test' };
|
||||
var results = diff(target, source);
|
||||
expect(results).to.have.property('keys');
|
||||
expect(results.keys).to.eql(['foo', 'test']);
|
||||
});
|
||||
|
||||
it('should ignore functions', function () {
|
||||
var target = { foo: 'bar', test: 'foo' };
|
||||
var source = { foo: 'test', fn: _.noop };
|
||||
diff(target, source);
|
||||
expect(target).to.not.have.property('fn');
|
||||
});
|
||||
|
||||
it('should ignore underscores', function () {
|
||||
var target = { foo: 'bar', test: 'foo' };
|
||||
var source = { foo: 'test', _private: 'foo' };
|
||||
diff(target, source);
|
||||
expect(target).to.not.have.property('_private');
|
||||
});
|
||||
|
||||
it('should ignore dollar signs', function () {
|
||||
var target = { foo: 'bar', test: 'foo' };
|
||||
var source = { foo: 'test', $private: 'foo' };
|
||||
diff(target, source);
|
||||
expect(target).to.not.have.property('$private');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue