updates to the courier to properly support doc based fetch and related improvements

This commit is contained in:
Spencer Alger 2014-02-22 12:09:44 -07:00
parent 65d8df1784
commit ebe67a39eb
19 changed files with 651 additions and 270 deletions

View file

@ -9,6 +9,7 @@ define(function (require) {
var DocSource = require('courier/data_source/doc');
var SearchSource = require('courier/data_source/search');
var HastyRefresh = require('courier/errors').HastyRefresh;
var nextTick = require('utils/next_tick');
// map constructors to type keywords
var sourceTypes = {
@ -29,6 +30,7 @@ define(function (require) {
courier._refs.search,
function (err) {
if (err) return courier._error(err);
courier._activeSearchRequest = null;
});
},
@ -36,10 +38,7 @@ define(function (require) {
// then fetch the onces that are not
doc: function (courier) {
DocSource.validate(courier, courier._refs.doc, function (err, invalid) {
if (err) {
courier.stop();
return courier.emit('error', err);
}
if (err) return courier._error(err);
// if all of the docs are up to date we don't need to do anything else
if (invalid.length === 0) return;
@ -54,7 +53,7 @@ define(function (require) {
// default config values
var defaults = {
fetchInterval: 30000,
docInterval: 2500
docInterval: 1500
};
/**
@ -63,12 +62,13 @@ define(function (require) {
* search:
* - inherits filters, and other query properties
* - automatically emit results on a set interval
*
* doc:
* - tracks doc versions
* - emits same results event when the doc is updated
* - helps seperate versions of kibana running on the same machine stay in sync
* - (NI) tracks version and uses it when new versions of a doc are reindexed
* - (NI) helps deal with conflicts
* - tracks version and uses it to verify that updates are safe to make
* - emits conflict event when that happens
*
* @param {object} config
* @param {Client} config.client - The elasticsearch.js client to use for querying. Should be
@ -115,7 +115,7 @@ define(function (require) {
// store a quick "bound" method for triggering
this._onInterval[type] = function () {
if (courier._refs[type].length) onFetch[type](courier);
courier.fetch(type);
courier._schedule(type);
};
@ -145,7 +145,7 @@ define(function (require) {
// is the courier currently running?
Courier.prototype.running = function () {
return !!this._fetchTimer;
return !!_.size(this._timer);
};
// stop the courier from fetching more results
@ -164,11 +164,18 @@ define(function (require) {
}, this);
};
// force a fetch of all datasources right now
Courier.prototype.fetch = function () {
_.forOwn(onFetch, function (fn, type) {
if (this._refs[type].length) fn(this);
}, this);
// force a fetch of all datasources right now, optionally filter by type
Courier.prototype.fetch = function (onlyType) {
var courier = this;
nextTick(function () {
_.forOwn(onFetch, function (fn, type) {
if (onlyType && onlyType !== type) return;
if (courier._refs[type].length) fn(courier);
courier._refs[type].forEach(function (ref) {
ref.fetchCount ++;
});
});
});
};
// data source factory
@ -181,6 +188,7 @@ define(function (require) {
return new Constructor(this, initialState);
};
/*****
* PRIVATE API
*****/
@ -206,7 +214,8 @@ define(function (require) {
var refs = this._refs[source._getType()];
if (!_.find(refs, { source: source })) {
refs.push({
source: source
source: source,
fetchCount: 0
});
}
};
@ -240,18 +249,15 @@ define(function (require) {
_.each(this._refs.doc, function (ref) {
var state = ref.source._state;
if (
state === updated
|| (
state.id === updated.id
&& state.type === updated.type
&& state.index === updated.index
)
state.id === updated.id
&& state.type === updated.type
&& state.index === updated.index
) {
delete ref.version;
}
});
onFetch.doc(this);
this.fetch('doc');
};
return Courier;

View file

@ -45,6 +45,11 @@ define(function (require) {
return courier.createSource(this._getType()).inherits(this);
};
this.courier = function (newCourier) {
courier = this._courier = newCourier;
return this;
};
// get/set internal state values
this._methods.forEach(function (name) {
this[name] = function (val) {
@ -96,6 +101,50 @@ define(function (require) {
return JSON.stringify(this.toJSON());
};
/**
* Set the $scope for a datasource, when a datasource is bound
* to a scope, it's event listeners will be wrapped in a call to that
* scope's $apply method (safely).
*
* This also binds the DataSource to the lifetime of the scope: when the scope
* is destroyed, the datasource is closed
*
* @param {AngularScope} $scope - the scope where the event emitter "occurs",
* helps angular determine where to start checking for changes
* @return {this} - chainable
*/
DataSource.prototype.$scope = function ($scope) {
var emitter = this;
if (emitter._emitter$scope) {
emitter._emitter$scope = $scope;
return this;
}
emitter._emitter$scope = $scope;
var origOn = emitter.on;
emitter.on = function (event, listener) {
var wrapped = function () {
var args = arguments;
// always use the stored ref so that it can be updated if needed
var $scope = emitter._emitter$scope;
$scope[$scope.$$phase ? '$eval' : '$apply'](function () {
listener.apply(emitter, args);
});
};
wrapped.listener = listener;
return origOn.call(emitter, event, wrapped);
};
emitter.on.restore = function () {
delete emitter._emitter$scope;
emitter.on = origOn;
};
return this;
};
/*****
* PRIVATE API
*****/

View file

@ -1,6 +1,7 @@
define(function (require) {
var DataSource = require('courier/data_source/data_source');
var inherits = require('utils/inherits');
var nextTick = require('utils/next_tick');
var errors = require('courier/errors');
var listenerCount = require('utils/event_emitter').listenerCount;
var _ = require('lodash');
@ -23,7 +24,7 @@ define(function (require) {
DocSource.fetch = function (courier, refs, cb) {
var client = courier._getClient();
var allRefs = [];
var body = {
var getBody = {
docs: []
};
@ -32,10 +33,10 @@ define(function (require) {
if (source._getType() !== 'doc') return;
allRefs.push(ref);
body.docs.push(source._flatten());
getBody.docs.push(source._flatten());
});
return client.mget({ body: body }, function (err, resp) {
return client.mget({ body: getBody }, function (err, resp) {
if (err) return cb(err);
_.each(resp.docs, function (resp, i) {
@ -43,9 +44,14 @@ define(function (require) {
var source = ref.source;
if (resp.error) return source._error(new errors.DocFetchFailure(resp));
if (ref.version === resp._version) return; // no change
ref.version = resp._version;
source._storeVersion(resp._version);
if (resp.found) {
if (ref.version === resp._version) return; // no change
ref.version = resp._version;
source._storeVersion(resp._version);
} else {
ref.version = void 0;
source._clearVersion();
}
source.emit('results', resp);
});
@ -63,9 +69,10 @@ define(function (require) {
DocSource.validate = function (courier, refs, cb) {
var invalid = _.filter(refs, function (ref) {
var storedVersion = ref.source._getVersion();
if (ref.version !== storedVersion) return true;
/* jshint eqeqeq: false */
return (!ref.fetchCount || ref.version != storedVersion);
});
setTimeout(function () {
nextTick(function () {
cb(void 0, invalid);
});
};
@ -102,6 +109,7 @@ define(function (require) {
id: state.id,
type: state.type,
index: state.index,
version: source._getVersion(),
body: {
doc: fields
}
@ -129,7 +137,6 @@ define(function (require) {
id: state.id,
type: state.type,
index: state.index,
version: source._getVersion(),
body: body,
ignore: [409]
}, function (err, resp) {
@ -201,8 +208,8 @@ define(function (require) {
* @return {number} - the version number, or NaN
*/
DocSource.prototype._getVersion = function () {
var id = this._versionKey();
return _.parseInt(localStorage.getItem(id));
var v = localStorage.getItem(this._versionKey());
return v ? _.parseInt(v) : void 0;
};
/**
@ -212,8 +219,17 @@ define(function (require) {
*/
DocSource.prototype._storeVersion = function (version) {
var id = this._versionKey();
localStorage.setItem(id, version);
if (version) {
localStorage.setItem(id, version);
} else {
localStorage.removeItem(id);
}
};
/**
* Clears the stored version for a DocSource
*/
DocSource.prototype._clearVersion = DocSource.prototype._storeVersion;
return DocSource;
});

View file

@ -1,37 +1,66 @@
define(function (require) {
var listenerCount = require('utils/event_emitter').listenerCount;
var _ = require('lodash');
var errors = {};
var inherits = require('utils/inherits');
// caused by a refresh attempting to start before the prevous is done
function HastyRefresh() {
this.name = 'HastyRefresh';
this.message = 'Courier attempted to start a query before the previous had finished.';
var canStack = (function () {
var err = new Error();
return !!err.stack;
}());
// abstract error class
function CourierError(msg, constructor) {
this.message = msg;
Error.call(this, this.message);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, constructor || CourierError);
} else if (canStack) {
this.stack = (new Error()).stack;
} else {
this.stack = '';
}
}
HastyRefresh.prototype = new Error();
HastyRefresh.prototype.constructor = HastyRefresh;
errors.HastyRefresh = HastyRefresh;
errors.CourierError = CourierError;
inherits(CourierError, Error);
/**
* HastyRefresh error class
* @param {String} [msg] - An error message that will probably end up in a log.
*/
errors.HastyRefresh = function HastyRefresh() {
CourierError.call(this,
'Courier attempted to start a query before the previous had finished.',
errors.HastyRefresh);
};
inherits(errors.HastyRefresh, CourierError);
/**
* DocFetchFailure Error - where there is an error getting a doc
* @param {String} [msg] - An error message that will probably end up in a log.
*/
errors.DocFetchFailure = function DocFetchFailure(resp) {
CourierError.call(this,
'Failed to get the doc: ' + JSON.stringify(resp),
errors.DocFetchFailure);
// where there is an error getting a doc
function DocFetchFailure(resp) {
this.name = 'DocFetchFailure';
this.resp = resp;
this.message = 'Failed to get the doc: ' + JSON.stringify(resp);
}
DocFetchFailure.prototype = new Error();
DocFetchFailure.prototype.constructor = DocFetchFailure;
errors.DocFetchFailure = DocFetchFailure;
};
inherits(errors.DocFetchFailure, CourierError);
/**
* Connection Error
* @param {String} [msg] - An error message that will probably end up in a log.
*/
errors.VersionConflict = function VersionConflict(resp) {
CourierError.call(this,
'Failed to store document changes do to a version conflict.',
errors.VersionConflict);
// there was a conflict storing a doc
function VersionConflict(resp) {
this.name = 'VersionConflict';
this.resp = resp;
this.message = 'Failed to store document changes do to a version conflict.';
}
VersionConflict.prototype = new Error();
VersionConflict.prototype.constructor = VersionConflict;
errors.VersionConflict = VersionConflict;
};
inherits(errors.VersionConflict, CourierError);
return errors;
});

View file

@ -0,0 +1,73 @@
define(function (require) {
var angular = require('angular');
angular
.module('kibana/directives')
.directive('configTest', function () {
return {
restrict: 'E',
template: 'My favorite number is {{favoriteNum}} <button ng-click="click()">New Favorite</button>',
controller: function ($scope, config) {
config.bind($scope, 'favoriteNum', {
default: 0
});
$scope.click = function () {
$scope.favoriteNum++;
};
}
};
})
.directive('courierTest', function () {
return {
restrict: 'E',
scope: {
type: '@'
},
template: '<strong style="float:left">{{count}} :&nbsp;</strong><pre>{{json}}</pre>',
controller: function ($scope, courier) {
$scope.count = 0;
var source = courier.rootSearchSource.extend()
.type($scope.type)
.source({
include: 'country'
})
.$scope($scope)
.on('results', function (resp) {
$scope.count ++;
$scope.json = JSON.stringify(resp.hits, null, ' ');
});
}
};
})
.directive('courierDocTest', function () {
return {
restrict: 'E',
scope: {
id: '@',
type: '@',
index: '@'
},
template: '<strong style="float:left">{{count}} : <button ng-click="click()">reindex</button> :&nbsp;</strong><pre>{{json}}</pre>',
controller: function (courier, $scope) {
$scope.count = 0;
var currentSource;
$scope.click = function () {
if (currentSource) {
source.doIndex(currentSource);
}
};
var source = courier.createSource('doc')
.id($scope.id)
.type($scope.type)
.index($scope.index)
.$scope($scope)
.on('results', function (doc) {
currentSource = doc._source;
$scope.count ++;
$scope.json = JSON.stringify(doc, null, ' ');
});
}
};
});
});

View file

@ -0,0 +1 @@
<config-test></config-test>

View file

@ -12,7 +12,7 @@
<script>require(['main'], function () {});</script>
</head>
<body>
<div ng-controller="Kibana">
<div ng-controller="kibana">
<div ng-view></div>
</div>
</body>

View file

@ -0,0 +1,15 @@
define(function (require) {
var angular = require('angular');
/**
* broke this out so that it could be loaded before the application is
*/
angular.module('kibana/constants')
// This stores the Kibana revision number, @REV@ is replaced by grunt.
.constant('kbnVersion', '@REV@')
// Use this for cache busting partials
.constant('cacheBust', 'cache-bust=' + Date.now())
;
});

View file

@ -1,75 +1,19 @@
define(function (require) {
var angular = require('angular');
var _ = require('lodash');
var $ = require('jquery');
require('services/config');
require('services/courier');
angular.module('kibana/controllers')
.controller('Kibana', function (courier, $scope, $rootScope) {
$rootScope.dataSource = courier.createSource('search')
.index('_all')
.size(5);
.controller('kibana', function ($scope, courier) {
setTimeout(function () {
courier.start();
}, 15);
// this should be triggered from within the controlling application
setTimeout(_.bindKey(courier, 'start'), 15);
});
angular.module('kibana/directives')
.directive('courierTest', function () {
return {
restrict: 'E',
scope: {
type: '@'
},
template: '<strong style="float:left">{{count}} :&nbsp;</strong><pre>{{json}}</pre>',
controller: function ($rootScope, $scope, courier) {
$scope.count = 0;
var source = $rootScope.dataSource.extend()
.type($scope.type)
.source({
include: 'country'
})
.on('results', function (resp) {
$scope.count ++;
$scope.json = JSON.stringify(resp.hits, null, ' ');
});
courier.mapper.getFields($rootScope.dataSource, function (data) {
$scope.json = data;
});
$scope.$watch('type', source.type);
}
};
})
.directive('courierDocTest', function () {
return {
restrict: 'E',
scope: {
id: '@',
type: '@',
index: '@'
},
template: '<strong style="float:left">{{count}} : <button ng-click="click()">reindex</button> :&nbsp;</strong><pre>{{json}}</pre>',
controller: function (courier, $scope) {
$scope.count = 0;
var currentSource;
$scope.click = function () {
if (currentSource) {
source.update(currentSource);
}
};
var source = courier.createSource('doc')
.id($scope.id)
.type($scope.type)
.index($scope.index)
.on('results', function (doc) {
currentSource = doc._source;
$scope.count ++;
$scope.json = JSON.stringify(doc, null, ' ');
});
}
};
$scope.$on('$routeChangeSuccess', function () {
if (courier.running()) courier.fetch();
});
});
});

View file

@ -7,90 +7,69 @@ define(function (require) {
var $ = require('jquery');
var _ = require('lodash');
var scopedRequire = require('require');
var enableAsyncModules = require('utils/async_modules');
var setup = require('./setup');
require('elasticsearch');
require('angular-route');
// keep a reference to each module defined before boot, so that
// after boot it can define new features. Also serves as a flag.
var preBootModules = [];
// the functions needed to register different
// features defined after boot
var registerFns = {};
var app = angular.module('kibana', []);
enableAsyncModules(app);
var dependencies = [
'elasticsearch',
'ngRoute',
'kibana',
'ngRoute'
'kibana/controllers',
'kibana/directives',
'kibana/factories',
'kibana/services',
'kibana/filters',
'kibana/constants'
];
_('controllers directives factories services filters'.split(' '))
.map(function (type) { return 'kibana/' + type; })
.each(function (name) {
preBootModules.push(angular.module(name, []));
dependencies.push(name);
});
function isScope(obj) {
return obj && obj.$evalAsync && obj.$watch;
}
var app = angular.module('kibana', dependencies);
// This stores the Kibana revision number, @REV@ is replaced by grunt.
app.constant('kbnVersion', '@REV@');
// Use this for cache busting partials
app.constant('cacheBust', 'cache-bust=' + Date.now());
/**
* Modules that need to register components within the application after
* bootstrapping is complete need to pass themselves to this method.
*
* @param {object} module - The Angular module
* @return {object} module
*/
app.useModule = function (module) {
if (preBootModules) {
preBootModules.push(module);
} else {
_.extend(module, registerFns);
dependencies.forEach(function (name) {
if (name.indexOf('kibana/') === 0) {
app.useModule(angular.module(name, []));
}
return module;
};
app.config(function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
$routeProvider
.when('/courier-test', {
templateUrl: 'courier/test.html',
})
.otherwise({
redirectTo: 'courier-test'
});
// this is how the internet told me to dynamically add modules :/
registerFns.controller = $controllerProvider.register;
registerFns.directive = $compileProvider.directive;
registerFns.factory = $provide.factory;
registerFns.service = $provide.service;
registerFns.filter = $filterProvider.register;
});
// load the core components
require([
'services/courier',
'services/es',
'services/config',
'controllers/kibana'
], function () {
app.requires = dependencies;
// bootstrap the app
$(function () {
angular
.bootstrap(document, dependencies)
.invoke(function ($rootScope) {
_.each(preBootModules, function (module) {
_.extend(module, registerFns);
});
preBootModules = false;
});
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'kibana/partials/index.html'
})
.when('/config-test', {
templateUrl: 'courier/tests/config.html',
})
.when('/courier-test', {
templateUrl: 'courier/tests/index.html',
})
.otherwise({
redirectTo: ''
});
});
setup(app, function (err) {
if (err) throw err;
// load the elasticsearch service
require([
'controllers/kibana',
'courier/test_directives',
'constants/base'
], function () {
// bootstrap the app
$(function () {
angular
.bootstrap(document, dependencies);
});
});
});

View file

@ -0,0 +1,4 @@
<ul>
<li><a ng-href="#/courier-test">Courier Test</a></li>
<li><a ng-href="#/config-test">Config Test</a></li>
</ul>

View file

@ -16,6 +16,7 @@
var bowerComponents = [
'angular',
'angular-route',
['async', 'lib/async'],
'd3',
['elasticsearch', 'elasticsearch.angular'],
'jquery',

View file

@ -1,89 +1,149 @@
define(function (require) {
var angular = require('angular');
var configFile = require('../../config');
var _ = require('lodash');
var configFile = require('../../config');
var nextTick = require('utils/next_tick');
require('services/courier');
var module = angular.module('kibana/services');
module.service('config', function ($q, es, courier) {
var app = angular.module('kibana');
var config = {};
// share doc and val cache between apps
var doc;
var vals = {};
module.service('config', function ($q, $rootScope, courier, kbnVersion) {
var watchers = {};
var unwatchers = [];
function watch(key, onChange) {
// probably a horrible idea
if (!watchers[key]) watchers[key] = [];
watchers[key].push(onChange);
if (!doc) {
doc = courier.createSource('doc')
.index(configFile.kibanaIndex)
.type('config')
.id(kbnVersion);
} else {
// clean up after previous app
doc.removeAllListeners('results');
doc.courier(courier);
}
function change(key, val) {
if (config[key] !== val) {
var oldVal = config[key];
config[key] = val;
if (watchers[key]) {
watchers[key].forEach(function (watcher) {
watcher(val, oldVal);
});
}
}
}
function getDoc() {
var defer = $q.promise();
courier.get({
index: config.kibanaIndex,
type: 'config',
id: app.constant('kbnVersion')
}, function fetchDoc(err, doc) {
_.assign(config, doc);
defer.resolve();
}, function onDocUpdate(doc) {
_.forOwn(doc, function (val, key) {
change(key, val);
});
doc.on('results', function (resp) {
if (!resp.found) return; // init should ensure it exists
_.forOwn(resp._source, function (val, key) {
if (vals[key] !== val) _change(key, val);
});
});
/******
* PUBLIC API
******/
function init() {
var defer = $q.defer();
courier.fetch();
doc.on('results', function completeInit(resp) {
// ONLY ACT IF !resp.found
if (!resp.found) {
console.log('creating empty config doc');
doc.doIndex({});
return;
}
console.log('fetched config doc');
doc.removeListener('results', completeInit);
defer.resolve();
});
return defer.promise;
}
return {
get: function (key) {
return config[key];
},
set: function (key, val) {
// sets a value in the config
// the es doc must be updated successfully for the update to reflect in the get api.
if (key === 'elasticsearch' || key === 'kibanaIndex') {
return $q.reject(new Error('These values must be updated in the config.js file.'));
}
function get(key) {
return vals[key];
}
function set(key, val) {
// sets a value in the config
// the es doc must be updated successfully for the update to reflect in the get api.
if (vals[key] === val) {
var defer = $q.defer();
if (config[key] === val) {
defer.resolve();
return defer.promise;
}
var body = {};
body[key] = val;
courier.update({
index: config.kibanaIndex,
type: 'config',
id: app.constant('kbnVersion'),
body: body
}, function (err) {
if (err) return defer.reject(err);
change(key, val);
defer.resolve();
});
defer.resolve(true);
return defer.promise;
},
$watch: watch,
init: getDoc
};
}
var update = {};
update[key] = val;
return doc.doUpdate(update)
.then(function () {
_change(key, val);
return true;
})
.catch(function (err) {
throw err;
});
}
function $watch(key, onChange) {
// probably a horrible idea
if (!watchers[key]) watchers[key] = [];
watchers[key].push(onChange);
_notify(onChange, vals[key]);
}
function bindToScope($scope, key, opts) {
$watch(key, function (val) {
if (opts && val === void 0) val = opts['default'];
$scope[key] = val;
});
var first = true;
unwatchers.push($scope.$watch(key, function (newVal) {
if (first) return first = false;
set(key, newVal);
}));
}
function close() {
watchers = null;
unwatchers.forEach(function (unwatcher) {
unwatcher();
});
}
// expose public API on the instance
this.init = init;
this.close = close;
this.get = get;
this.set = set;
this.bind = bindToScope;
this.$watch = $watch;
/*******
* PRIVATE API
*******/
function _change(key, val) {
_notify(watchers[key], val, vals[key]);
vals[key] = val;
console.log(key, 'is now', val);
}
function _notify(fns, cur, prev) {
if ($rootScope.$$phase) {
// reschedule
nextTick(function () {
_notify(fns, cur, prev);
});
return;
}
var isArr = _.isArray(fns);
if (!fns || (isArr && !fns.length)) return;
$rootScope.$apply(function () {
if (!isArr) return fns(cur, prev);
fns.forEach(function (onChange) {
onChange(cur, prev);
});
});
}
});
});

View file

@ -5,16 +5,19 @@ define(function (require) {
var errors = require('courier/errors');
require('services/promises');
require('services/es');
var courier; // share the courier amoungst all of the apps
angular.module('kibana/services')
.service('courier', function (es, promises) {
if (courier) return courier;
promises.playNice(DocSource.prototype, [
'doUpdate',
'doIndex'
]);
var courier = new Courier({
courier = new Courier({
fetchInterval: 15000,
client: es,
promises: promises
@ -22,6 +25,8 @@ define(function (require) {
courier.errors = errors;
courier.rootSearchSource = courier.createSource('search');
return courier;
});
});

View file

@ -1,8 +1,16 @@
define(function (require) {
var angular = require('angular');
var configFile = require('../../config');
var module = angular.module('kibana/services');
module.service('es', function (esFactory) {
return esFactory();
});
var es; // share the client amoungst all apps
require('angular')
.module('kibana/services')
.service('es', function (esFactory, $q) {
if (es) return es;
es = esFactory({
host: configFile.elasticsearch
});
return es;
});
});

101
src/kibana/setup.js Normal file
View file

@ -0,0 +1,101 @@
define(function (require) {
var angular = require('angular');
var async = require('async');
var $ = require('jquery');
var configFile = require('../config');
var nextTick = require('utils/next_tick');
/**
* Setup the kibana application, ensuring that the kibanaIndex exists,
* and perform any migration of data that is required.
*
* @param {Module} app - The Kibana module
* @param {function} done - callback
*/
return function SetupApp(app, done) {
// load angular deps
require([
'elasticsearch',
'services/es',
'services/config',
'constants/base'
], function () {
$(function () {
var setup = angular.module('setup', [
'elasticsearch',
'kibana/services',
'kibana/constants'
]);
var appEl = document.createElement('div');
var kibanaIndexExists;
setup.run(function (es, config) {
// init the setup module
async.series([
async.apply(checkForKibanaIndex, es),
async.apply(createKibanaIndex, es),
async.apply(checkForCurrentConfigDoc, es),
async.apply(initConfig, config)
], function (err) {
// ready to go, remove the appEl, close services and boot be done
appEl.remove();
console.log('booting application');
return done(err);
});
});
angular.bootstrap(appEl, ['setup']);
function checkForKibanaIndex(es, done) {
console.log('look for kibana index');
es.indices.exists({
index: configFile.kibanaIndex
}, function (err, exists) {
console.log('kibana index does', (exists ? '' : 'not ') + 'exist');
kibanaIndexExists = exists;
return done(err);
});
}
// create the index if it doens't exist already
function createKibanaIndex(es, done) {
if (kibanaIndexExists) return done();
console.log('creating kibana index');
es.indices.create({
index: configFile.kibanaIndex,
body: {
settings: {
mappings: {
type1: {
_source: {
enabled: false
},
properties: {
field1: {
type: 'string',
index: 'not_analyzed'
}
}
}
}
}
}
}, done);
}
// if the index is brand new, no need to see if it is out of data
function checkForCurrentConfigDoc(es, done) {
if (!kibanaIndexExists) return done();
console.log('checking if migration is necessary: not implemented');
nextTick(done);
}
function initConfig(config, done) {
console.log('initializing config service');
config.init().then(function () { done(); }, done);
}
});
});
};
});

View file

@ -0,0 +1,57 @@
define(function (require) {
var _ = require('lodash');
// TODO: this will probably fail to work when we have multiple apps. Might need to propogate
// registrations to multiple providers
function enable(app) {
// keep a reference to each module defined before boot, so that
// after boot it can define new features. Also serves as a flag.
var preBootModules = [];
// the functions needed to register different
// features defined after boot
var registerFns = {};
app.config(function ($controllerProvider, $compileProvider, $filterProvider, $provide) {
// this is how the internet told me to dynamically add modules :/
registerFns = {
controller: $controllerProvider.register,
directive: $compileProvider.directive,
factory: $provide.factory,
service: $provide.service,
constant: $provide.constant,
value: $provide.value,
filter: $filterProvider.register
};
});
/**
* Modules that need to register components within the application after
* bootstrapping is complete need to pass themselves to this method.
*
* @param {object} module - The Angular module
* @return {object} module
*/
app.useModule = function (module) {
if (preBootModules) {
preBootModules.push(module);
} else {
_.extend(module, registerFns);
}
return module;
};
/**
* Called after app is bootrapped to enable asyncModules
* @return {[type]} [description]
*/
app.run(function () {
_.each(preBootModules, function (module) {
_.extend(module, registerFns);
});
preBootModules = false;
});
}
return enable;
});

View file

@ -0,0 +1,33 @@
define(function () {
var canSetImmediate = typeof window !== 'undefined' && window.setImmediate;
var canPost = typeof window !== 'undefined' && window.postMessage && window.addEventListener
;
if (canSetImmediate) {
return function (f) { return window.setImmediate(f); };
}
if (canPost) {
var queue = [];
window.addEventListener('message', function (ev) {
if (ev.source === window && ev.data === 'process-tick') {
ev.stopPropagation();
if (queue.length > 0) {
var fn = queue.shift();
fn();
}
}
}, true);
return function nextTick(fn) {
queue.push(fn);
window.postMessage('process-tick', '*');
};
}
return function nextTick(fn) {
setTimeout(fn, 0);
};
});