mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
added saved_object component, which generalizes the saved_____ stuff
This commit is contained in:
parent
8abbc82475
commit
f441d2d421
17 changed files with 447 additions and 170 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
node_modules
|
||||
!src/bower_components/**/*
|
|
@ -19,25 +19,23 @@ define(function (require) {
|
|||
gridster,
|
||||
widgets;
|
||||
|
||||
if (_.isUndefined($scope.control) || _.isUndefined($scope.grid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
elem.addClass('gridster');
|
||||
|
||||
width = elem.width();
|
||||
|
||||
var init = function () {
|
||||
initGrid();
|
||||
elem.on('click', 'li i.remove', function (event) {
|
||||
var target = event.target.parentNode.parentNode;
|
||||
gridster.remove_widget(target);
|
||||
});
|
||||
|
||||
$scope.control.unserializeGrid($scope.grid);
|
||||
$scope.$watch('grid', function () {
|
||||
initGrid();
|
||||
$scope.control.unserializeGrid($scope.grid);
|
||||
});
|
||||
};
|
||||
|
||||
var initGrid = function () {
|
||||
var initGrid = _.once(function () {
|
||||
gridster = elem.gridster({
|
||||
autogenerate_stylesheet: false,
|
||||
widget_margins: [5, 5],
|
||||
|
@ -58,13 +56,17 @@ define(function (require) {
|
|||
}
|
||||
}).data('gridster');
|
||||
gridster.generate_stylesheet({namespace: '.gridster'});
|
||||
};
|
||||
});
|
||||
|
||||
$scope.control.clearGrid = function (cb) {
|
||||
gridster.remove_all_widgets();
|
||||
};
|
||||
|
||||
$scope.control.unserializeGrid = function (grid) {
|
||||
if (typeof grid === 'string') {
|
||||
grid = JSON.stringify(grid);
|
||||
}
|
||||
gridster.remove_all_widgets();
|
||||
_.each(grid, function (panel) {
|
||||
$scope.control.addWidget(panel);
|
||||
});
|
||||
|
|
|
@ -3,82 +3,44 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
var inherits = require('utils/inherits');
|
||||
|
||||
require('saved_object/saved_object');
|
||||
|
||||
// Used only by the savedDashboards service, usually no reason to change this
|
||||
module.factory('SavedDashboard', function (configFile, courier, Promise, createNotifier, CouriersDocSource) {
|
||||
module.factory('SavedDashboard', function (configFile, courier, Promise, createNotifier, SavedObject) {
|
||||
|
||||
// Create a notified for setting alerts
|
||||
var notify = createNotifier({
|
||||
location: 'Saved Dashboard'
|
||||
});
|
||||
|
||||
// SavedDashboard constructor. Usually you'd interact with an instance of this
|
||||
// ID is option, otherwise one will be generated on save.
|
||||
// SavedDashboard constructor. Usually you'd interact with an instance of this.
|
||||
// ID is option, without it one will be generated on save.
|
||||
function SavedDashboard(id) {
|
||||
SavedObject.call(this, {
|
||||
// this object will be saved at {{configFile.kibanaIndex}}/dashboard/{{id}}
|
||||
type: 'dashboard',
|
||||
|
||||
// Keep a reference to this
|
||||
var dash = this;
|
||||
// if this is ==null then the SavedObject will be assigned the defaults
|
||||
id: id,
|
||||
|
||||
// Intializes a docSource for dash
|
||||
CouriersDocSource.call(dash, courier);
|
||||
// if the index is not defined, we will push this mapping into ES
|
||||
mapping: {
|
||||
title: 'string',
|
||||
hits: 'integer',
|
||||
description: 'string',
|
||||
panelsJSON: 'string'
|
||||
},
|
||||
|
||||
// Wrap this once so that accidental re-init's don't cause extra ES calls
|
||||
dash.init = _.once(function () {
|
||||
// If we haven't saved to ES, there's no point is asking ES for the dashboard
|
||||
// just return whatever we have
|
||||
if (dash.unsaved) return Promise.resolved(dash);
|
||||
// defeault values to assign to the doc
|
||||
defaults: {
|
||||
title: 'New Dashboard',
|
||||
hits: 0,
|
||||
description: '',
|
||||
panelsJSON: '[]'
|
||||
},
|
||||
|
||||
// Otherwise, get the dashboard.
|
||||
return dash.fetch().then(function applyUpdate(resp) {
|
||||
if (!resp.found) throw new Error('Unable to find that Dashboard...');
|
||||
|
||||
// Since the dashboard was found, we know it has been saved before
|
||||
dash.unsaved = false;
|
||||
|
||||
// Set the ID of our docSource based on ES response
|
||||
dash.set('id', resp._id);
|
||||
|
||||
// Give dash.details all of the properties of _source
|
||||
_.assign(dash.details, resp._source);
|
||||
|
||||
// Any time dash is updated, re-call applyUpdate
|
||||
dash.onUpdate().then(applyUpdate, notify.fatal);
|
||||
|
||||
return dash;
|
||||
});
|
||||
// should a search source be made available for this SavedObject
|
||||
searchSource: false
|
||||
});
|
||||
|
||||
// Properties needed for Elasticsearch
|
||||
dash.index(configFile.kibanaIndex)
|
||||
.type('dashboard')
|
||||
.id(id || void 0);
|
||||
|
||||
// If we need to do anything different on first save, we know if the dash is unsaved
|
||||
// eg, change location.url on first save
|
||||
// If there is no id passed in, the dashboard has not been saved yet.
|
||||
dash.unsaved = !id;
|
||||
|
||||
// Attach some new properties in a new object so they don't collide
|
||||
// Effectively the contents of _source
|
||||
dash.details = {
|
||||
title: 'New Dashboard',
|
||||
panels: []
|
||||
};
|
||||
|
||||
// Persist our dash object back into Elasticsearch
|
||||
dash.save = function () {
|
||||
|
||||
// dash.doIndex stores dash.detail as the document in elasticxsearch
|
||||
return dash.doIndex(dash.details)
|
||||
.then(function (id) {
|
||||
dash.set('id', id);
|
||||
return id;
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Sets savedDashboard.prototype to an instance of CourierDocSource
|
||||
inherits(SavedDashboard, CouriersDocSource);
|
||||
// Sets savedDashboard.prototype to an instance of SavedObject
|
||||
inherits(SavedDashboard, SavedObject);
|
||||
|
||||
return SavedDashboard;
|
||||
});
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<span class="navbar-brand pull-left" ng-click="editingTitle = true" ng-hide="editingTitle">
|
||||
{{dash.details.title}}
|
||||
{{dash.title}}
|
||||
</span>
|
||||
<span class="pull-left" ng-show="editingTitle">
|
||||
<form class="navbar-form" ng-submit="editingTitle = false">
|
||||
<input type="text" ng-model="dash.details.title" class="form-control"/>
|
||||
<input type="text" ng-model="dash.title" class="form-control"/>
|
||||
</form>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -22,7 +22,7 @@
|
|||
<config config-template="configTemplate" config-object="configurable" config-close="configClose" config-submit="configSubmit"></config>
|
||||
|
||||
<div class="container-default">
|
||||
<ul dashboard-grid grid="dash.details.panels" control="gridControl"></ul>
|
||||
<ul dashboard-grid grid="panels" control="gridControl"></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -61,39 +61,41 @@ define(function (require) {
|
|||
|
||||
// All inputs go here.
|
||||
$scope.input = {
|
||||
search: ''
|
||||
search: void 0
|
||||
};
|
||||
|
||||
// Setup configurable values for config directive, after objects are initialized
|
||||
$scope.configurable = {
|
||||
dashboard: dash.details,
|
||||
dashboard: dash,
|
||||
input: $scope.input
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', _.bindKey(dash, 'cancelPending'));
|
||||
$scope.$on('$destroy', dash.destroy);
|
||||
|
||||
var dashboardSearch = function () {
|
||||
//ignore first run, just the watcher getting initialized
|
||||
$scope.$watch('dash.panelsJSON', function (val) {
|
||||
$scope.panels = JSON.parse(val || '[]');
|
||||
});
|
||||
|
||||
dashboardSearch = function (query) {
|
||||
if (_.isString(query) && query.length > 0) {
|
||||
query = {match: {title: {query: query, type: 'phrase_prefix'}}};
|
||||
} else {
|
||||
query = {match_all: {}};
|
||||
var dashboardSearch = function (query) {
|
||||
if (query === void 0) return;
|
||||
|
||||
if (_.isString(query) && query.length > 0) {
|
||||
query = {match: {title: {query: query, type: 'phrase_prefix'}}};
|
||||
} else {
|
||||
query = {match_all: {}};
|
||||
}
|
||||
|
||||
es.search({
|
||||
index: configFile.kibanaIndex,
|
||||
type: 'dashboard',
|
||||
size: 10,
|
||||
body: {
|
||||
query: query
|
||||
}
|
||||
|
||||
es.search({
|
||||
index: configFile.kibanaIndex,
|
||||
type: 'dashboard',
|
||||
size: 10,
|
||||
body: {
|
||||
query: query
|
||||
}
|
||||
})
|
||||
.then(function (res) {
|
||||
$scope.configurable.searchResults = res.hits.hits;
|
||||
});
|
||||
};
|
||||
})
|
||||
.then(function (res) {
|
||||
$scope.configurable.searchResults = res.hits.hits;
|
||||
});
|
||||
};
|
||||
$scope.$watch('configurable.input.search', dashboardSearch);
|
||||
|
||||
|
@ -118,12 +120,13 @@ define(function (require) {
|
|||
};
|
||||
|
||||
$scope.save = function () {
|
||||
var wasUnsaved = dash.unsaved;
|
||||
dash.details.panels = $scope.gridControl.serializeGrid();
|
||||
dash.panelsJSON = JSON.stringify($scope.gridControl.serializeGrid() || []);
|
||||
|
||||
return dash.save()
|
||||
.then(function (res) {
|
||||
if (wasUnsaved) $location.url('/dashboard/' + encodeURIComponent(dash.get('id')));
|
||||
if (dash.id !== $routeParams.id) {
|
||||
$location.url('/dashboard/' + encodeURIComponent(dash.id));
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
<input type="text" ng-model="configurable.input.search" class="form-control"/>
|
||||
</div>
|
||||
<ul class="nav nav-pills">
|
||||
<li ng-repeat="res in configurable.searchResults | orderBy:'_source.title'" ng-class="{active: configurable.dashboard.title == res._source.title}">
|
||||
<a ng-href="#/dashboard/{{res._id}}">{{res._source.title}}</a>
|
||||
<li
|
||||
ng-repeat="res in configurable.searchResults | orderBy:'_source.fields.title'"
|
||||
ng-class="{active: configurable.dashboard.title == res._source.fields.title}">
|
||||
<pre>{{res}}</pre>
|
||||
<a ng-href="#/dashboard/{{res._id}}">{{res._source.fields.title}}</a>
|
||||
</li>
|
||||
</ul>
|
|
@ -40,6 +40,8 @@ define(function (require) {
|
|||
* PUBLIC API
|
||||
******/
|
||||
|
||||
config.file = configFile;
|
||||
|
||||
/**
|
||||
* Executes once and returns a promise that is resolved once the
|
||||
* config has loaded for the first time.
|
||||
|
|
|
@ -155,25 +155,6 @@ define(function (require) {
|
|||
throw new Error('Aborting all pending requests failed.');
|
||||
}
|
||||
};
|
||||
|
||||
courier._docUpdated = function (doc) {
|
||||
var key = doc._versionKey();
|
||||
|
||||
// filter out the matching requests from the _pendingRequests queue
|
||||
var pending = this._pendingRequests;
|
||||
pending.splice(0).filter(function (req) {
|
||||
var isDoc = req.source._getType() === 'doc';
|
||||
var keyMatches = isDoc && req.source._versionKey() === key;
|
||||
|
||||
if (!keyMatches) {
|
||||
// put it back into the pending queue
|
||||
pending.push(req);
|
||||
return false;
|
||||
}
|
||||
|
||||
req.defer.resolve();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return new Courier();
|
||||
|
|
40
src/kibana/components/courier/data_source/_doc_send_to_es.js
Normal file
40
src/kibana/components/courier/data_source/_doc_send_to_es.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
return function (Promise, es, $injector) {
|
||||
var docUpdated = $injector.invoke(require('./_doc_updated'));
|
||||
|
||||
/**
|
||||
* Backend for doUpdate and doIndex
|
||||
* @param {String} method - the client method to call
|
||||
* @param {Boolean} validateVersion - should our knowledge
|
||||
* of the the docs current version be sent to es?
|
||||
* @param {String} body - HTTP request body
|
||||
*/
|
||||
return function (courier, method, validateVersion, body) {
|
||||
var doc = this;
|
||||
// straight assignment will causes undefined values
|
||||
var params = _.pick(this._state, ['id', 'type', 'index']);
|
||||
params.body = body;
|
||||
params.ignore = [409];
|
||||
|
||||
if (validateVersion && params.id) {
|
||||
params.version = doc._getVersion();
|
||||
}
|
||||
|
||||
return es[method](params)
|
||||
.then(function (resp) {
|
||||
if (resp.status === 409) throw new courier.errors.VersionConflict(resp);
|
||||
|
||||
doc._storeVersion(resp._version);
|
||||
docUpdated(courier, doc, null);
|
||||
doc.id(resp._id);
|
||||
return resp._id;
|
||||
})
|
||||
.catch(function (err) {
|
||||
// cast the error
|
||||
throw new courier.errors.RequestFailure(err);
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
29
src/kibana/components/courier/data_source/_doc_updated.js
Normal file
29
src/kibana/components/courier/data_source/_doc_updated.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
define(function (require) {
|
||||
return function (Promise) {
|
||||
/**
|
||||
* Notify other docs that docs like this one have been updated
|
||||
* @param {DocSource} doc - the doc that was updated, used to match other listening parties
|
||||
* @return {undefined}
|
||||
*/
|
||||
return function (courier, doc, body) {
|
||||
var key = doc._versionKey();
|
||||
|
||||
if (!body) body = doc.fetch();
|
||||
|
||||
// filter out the matching requests from the _pendingRequests queue
|
||||
var pending = courier._pendingRequests;
|
||||
pending.splice(0).filter(function (req) {
|
||||
var isDoc = req.source._getType() === 'doc';
|
||||
var keyMatches = isDoc && req.source._versionKey() === key;
|
||||
|
||||
if (!keyMatches) {
|
||||
// put it back into the pending queue
|
||||
pending.push(req);
|
||||
return false;
|
||||
}
|
||||
|
||||
Promise.cast(body).then(req.defer.resolve);
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
|
@ -143,7 +143,19 @@ define(function (require) {
|
|||
* @param {Function} cb - callback
|
||||
*/
|
||||
SourceAbstract.prototype.fetch = function () {
|
||||
return couriersFetch[this._getType()](this);
|
||||
var courier = this._courier;
|
||||
var source = this;
|
||||
return couriersFetch[this._getType()](this)
|
||||
.then(function (res) {
|
||||
courier._pendingRequests.splice(0).forEach(function (req) {
|
||||
if (req.source === source) {
|
||||
req.defer.resolve(_.cloneDeep(res));
|
||||
} else {
|
||||
courier._pendingRequests.push(req);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,15 +2,15 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
|
||||
var inherits = require('utils/inherits');
|
||||
var listenerCount = require('utils/event_emitter').listenerCount;
|
||||
|
||||
require('./abstract');
|
||||
|
||||
var module = require('modules').get('kibana/courier');
|
||||
|
||||
module.factory('CouriersDocSource', function (couriersErrors, CouriersSourceAbstract, Promise, es) {
|
||||
module.factory('CouriersDocSource', function (couriersErrors, CouriersSourceAbstract, Promise, es, $injector) {
|
||||
var VersionConflict = couriersErrors.VersionConflict;
|
||||
var RequestFailure = couriersErrors.RequestFailure;
|
||||
var sendToEs = $injector.invoke(require('./_doc_send_to_es'));
|
||||
|
||||
function DocSource(courier, initialState) {
|
||||
CouriersSourceAbstract.call(this, courier, initialState);
|
||||
|
@ -18,6 +18,8 @@ define(function (require) {
|
|||
// move onResults over to onUpdate, because that makes more sense
|
||||
this.onUpdate = this.onResults;
|
||||
this.onResults = void 0;
|
||||
|
||||
this._sendToEs = sendToEs;
|
||||
}
|
||||
inherits(DocSource, CouriersSourceAbstract);
|
||||
|
||||
|
@ -44,7 +46,7 @@ define(function (require) {
|
|||
*/
|
||||
DocSource.prototype.doUpdate = function (fields) {
|
||||
if (!this._state.id) return this.doIndex(fields);
|
||||
return this._sendToEs('update', true, { doc: fields });
|
||||
return this._sendToEs(this._courier, 'update', false, { doc: fields });
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -53,7 +55,7 @@ define(function (require) {
|
|||
* @return {[type]} [description]
|
||||
*/
|
||||
DocSource.prototype.doIndex = function (body) {
|
||||
return this._sendToEs('index', false, body);
|
||||
return this._sendToEs(this._courier, 'index', true, body);
|
||||
};
|
||||
|
||||
/*****
|
||||
|
@ -91,6 +93,8 @@ define(function (require) {
|
|||
*/
|
||||
DocSource.prototype._versionKey = function () {
|
||||
var state = this._state;
|
||||
|
||||
if (!state.index || !state.type || !state.id) return;
|
||||
return 'DocVersion:' + (
|
||||
[
|
||||
state.index,
|
||||
|
@ -118,7 +122,10 @@ define(function (require) {
|
|||
* @return {[type]} [description]
|
||||
*/
|
||||
DocSource.prototype._getStoredVersion = function () {
|
||||
var v = localStorage.getItem(this._versionKey());
|
||||
var key = this._versionKey();
|
||||
if (!key) return;
|
||||
|
||||
var v = localStorage.getItem(key);
|
||||
this._version = v ? _.parseInt(v) : void 0;
|
||||
return this._version;
|
||||
};
|
||||
|
@ -131,50 +138,19 @@ define(function (require) {
|
|||
DocSource.prototype._storeVersion = function (version) {
|
||||
if (!version) return this._clearVersion();
|
||||
|
||||
var id = this._versionKey();
|
||||
localStorage.setItem(id, version);
|
||||
var key = this._versionKey();
|
||||
if (!key) return;
|
||||
this._version = version;
|
||||
localStorage.setItem(key, version);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the stored version for a DocSource
|
||||
*/
|
||||
DocSource.prototype._clearVersion = function () {
|
||||
var id = this._versionKey();
|
||||
localStorage.removeItem(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Backend for doUpdate and doIndex
|
||||
* @param {String} method - the client method to call
|
||||
* @param {Boolean} validateVersion - should our knowledge
|
||||
* of the the docs current version be sent to es?
|
||||
* @param {String} body - HTTP request body
|
||||
*/
|
||||
DocSource.prototype._sendToEs = function (method, validateVersion, body) {
|
||||
var source = this;
|
||||
var courier = this._courier;
|
||||
|
||||
// straight assignment will causes undefined values
|
||||
var params = _.pick(this._state, ['id', 'type', 'index']);
|
||||
params.body = body;
|
||||
params.ignore = [409];
|
||||
|
||||
if (validateVersion) {
|
||||
params.version = source._getVersion();
|
||||
}
|
||||
|
||||
return es[method](params)
|
||||
.then(function (resp) {
|
||||
if (resp.status === 409) throw new VersionConflict(resp);
|
||||
|
||||
source._storeVersion(resp._version);
|
||||
courier._docUpdated(source);
|
||||
return resp._id;
|
||||
})
|
||||
.catch(function (err) {
|
||||
// cast the error
|
||||
return new RequestFailure(err);
|
||||
});
|
||||
var key = this._versionKey();
|
||||
if (!key) return;
|
||||
localStorage.removeItem(key);
|
||||
};
|
||||
|
||||
return DocSource;
|
||||
|
|
|
@ -50,6 +50,14 @@ define(function (require) {
|
|||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the parent of this SearchSource
|
||||
* @return {SearchSource}
|
||||
*/
|
||||
SearchSource.prototype.parent = function () {
|
||||
return this._parent;
|
||||
};
|
||||
|
||||
/******
|
||||
* PRIVATE APIS
|
||||
******/
|
||||
|
|
|
@ -31,6 +31,7 @@ define(function (require) {
|
|||
all.forEach(function (req) {
|
||||
req.defer.reject(err);
|
||||
});
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
88
src/kibana/components/saved_object/_mapping_setup.js
Normal file
88
src/kibana/components/saved_object/_mapping_setup.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
define(function () {
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Create the mappingSetup module by passing in it's dependencies.
|
||||
*/
|
||||
return function (configFile, es, courier) {
|
||||
var mappingSetup = {};
|
||||
|
||||
/**
|
||||
* Use to create the mappings, but that should only happen one at a time
|
||||
*/
|
||||
var activeTypeCreations = {};
|
||||
|
||||
/**
|
||||
* Get the list of type's mapped in elasticsearch
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
var getKnownKibanaTypes = _.once(function () {
|
||||
var indexName = configFile.kibanaIndex;
|
||||
return es.indices.getFieldMapping({
|
||||
// only concerned with types in this kibana index
|
||||
index: indexName,
|
||||
// check all types
|
||||
type: '*',
|
||||
// limit the response to just the _source field for each index
|
||||
field: '_source'
|
||||
}).then(function (resp) {
|
||||
return _.keys(resp[indexName].mappings);
|
||||
});
|
||||
});
|
||||
|
||||
mappingSetup.isDefined = function (type) {
|
||||
return getKnownKibanaTypes()
|
||||
.then(function (knownTypes) {
|
||||
// if the type is in the knownTypes array already
|
||||
return !!(~knownTypes.indexOf(type));
|
||||
});
|
||||
};
|
||||
|
||||
mappingSetup.setup = function (type, mapping) {
|
||||
// if there is already a creation running for this index type
|
||||
if (activeTypeCreations[type]) {
|
||||
// return a promise that will reexecute the setup once the
|
||||
// current is complete.
|
||||
return activeTypeCreations[type].then(function () {
|
||||
return mappingSetup.setup(type, mapping);
|
||||
});
|
||||
}
|
||||
|
||||
var prom = getKnownKibanaTypes()
|
||||
.then(function (knownTypes) {
|
||||
// if the type is in the knownTypes array already
|
||||
if (~knownTypes.indexOf(type)) return false;
|
||||
|
||||
// we need to create the mapping
|
||||
var body = {};
|
||||
body[type] = {
|
||||
properties: mapping
|
||||
};
|
||||
|
||||
return es.indices.putMapping({
|
||||
index: configFile.kibanaIndex,
|
||||
type: type,
|
||||
body: body
|
||||
}).then(function (resp) {
|
||||
// add this type to the list of knownTypes
|
||||
knownTypes.push(type);
|
||||
|
||||
// cast the response to "true", meaning
|
||||
// the mapping exists
|
||||
return true;
|
||||
});
|
||||
})
|
||||
// wether this fails or not, remove it from the activeTypeCreations obj
|
||||
// once complete
|
||||
.finally(function () {
|
||||
delete activeTypeCreations[type];
|
||||
});
|
||||
|
||||
activeTypeCreations[type] = prom;
|
||||
return prom;
|
||||
};
|
||||
|
||||
return mappingSetup;
|
||||
};
|
||||
|
||||
});
|
169
src/kibana/components/saved_object/saved_object.js
Normal file
169
src/kibana/components/saved_object/saved_object.js
Normal file
|
@ -0,0 +1,169 @@
|
|||
define(function (require) {
|
||||
var module = require('modules').get('kibana/saved_object');
|
||||
var _ = require('lodash');
|
||||
|
||||
module.factory('SavedObject', function (courier, configFile, Promise, createNotifier, $injector) {
|
||||
var mappingSetup = $injector.invoke(require('./_mapping_setup'));
|
||||
|
||||
function SavedObject(config) {
|
||||
if (!_.isObject(config)) config = {};
|
||||
|
||||
// save an easy reference to this
|
||||
var obj = this;
|
||||
|
||||
/************
|
||||
* Initialize config vars
|
||||
************/
|
||||
// the doc which is used to store this object
|
||||
var docSource = courier.createSource('doc');
|
||||
|
||||
// type name for this object, used as the ES-type
|
||||
var type = config.type;
|
||||
|
||||
// Create a notifier for sending alerts
|
||||
var notify = createNotifier({
|
||||
location: 'Saved ' + type
|
||||
});
|
||||
|
||||
// mapping definition for the fields that this object
|
||||
// will expose, the actual mapping will place this under it's
|
||||
// "fields:" propety definition.
|
||||
var fieldMapping = config.mapping || {};
|
||||
|
||||
// default field values, assigned when the source is loaded
|
||||
var defaults =
|
||||
|
||||
// optional search source which this object configures
|
||||
obj.searchSource = config.searchSource && courier.createSource('search');
|
||||
|
||||
// the id of the document
|
||||
obj.id = config.id || void 0;
|
||||
|
||||
/**
|
||||
* Asynchronously initialize this object - will only run
|
||||
* once even if called multiple times.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolved {SavedObject}
|
||||
*/
|
||||
this.init = _.once(function () {
|
||||
// ensure that the type is defined
|
||||
if (!type) throw new Error('You must define a type name to use SavedObject objects.');
|
||||
|
||||
// tell the docSource where to find the doc
|
||||
docSource
|
||||
.index(configFile.kibanaIndex)
|
||||
.type(type)
|
||||
.id(obj.id);
|
||||
|
||||
|
||||
// check that the mapping for this type is defined
|
||||
return mappingSetup.isDefined(type)
|
||||
.then(function (defined) {
|
||||
// if it is already defined skip this step
|
||||
if (defined) return true;
|
||||
|
||||
// we need to setup the mapping, flesh it out first
|
||||
var mapping = {
|
||||
// wrap the mapping in a "fields" key, so that it won't collide
|
||||
// with things we add, like "searchSource"
|
||||
fields: {
|
||||
// allow shortcuts for the field types, by just setting the value
|
||||
// to the type name
|
||||
properties: _.mapValues(mapping, function (val, prop) {
|
||||
if (typeof val !== 'string') return val;
|
||||
return {
|
||||
type: val
|
||||
};
|
||||
})
|
||||
},
|
||||
|
||||
// setup the searchSource mapping, even if it is not used but this type yet
|
||||
searchSourceJSON: {
|
||||
type: 'string'
|
||||
}
|
||||
};
|
||||
|
||||
// tell mappingSetup to set type
|
||||
return mappingSetup.setup(type, mapping);
|
||||
})
|
||||
.then(function () {
|
||||
// If there is not id, then there is no document to fetch from elasticsearch
|
||||
if (!obj.id) {
|
||||
// just assign the defaults and be done
|
||||
_.assign(obj, defaults);
|
||||
return false;
|
||||
}
|
||||
|
||||
// fetch the object from ES
|
||||
return docSource.fetch()
|
||||
.then(function applyDocSourceResp(resp) {
|
||||
if (!resp.found) throw new Error('Unable to find that ' + type + '.');
|
||||
|
||||
// assign the defaults to the response
|
||||
_.defaults(resp._source.fields, defaults);
|
||||
|
||||
// Give obj all of the values in _source.fields
|
||||
_.assign(obj, resp._source.fields);
|
||||
|
||||
// if we have a searchSource, set it's state based on the searchSourceJSON field
|
||||
if (obj.searchSource) {
|
||||
var state = {};
|
||||
try {
|
||||
state = JSON.parse(resp._source.searchSourceJSON);
|
||||
} catch (e) {}
|
||||
obj.searchSource.set(state);
|
||||
}
|
||||
|
||||
// Any time obj is updated, re-call applyDocSourceResp
|
||||
docSource.onUpdate().then(applyDocSourceResp, notify.fatal);
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
// return our obj as the result of init()
|
||||
return obj;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Save this object
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolved {String} - The id of the doc
|
||||
*/
|
||||
this.save = function () {
|
||||
var body = {
|
||||
fields: {}
|
||||
};
|
||||
|
||||
_.forOwn(fieldMapping, function (mapping, fieldName) {
|
||||
if (obj[fieldName] != null) {
|
||||
body.fields[fieldName] = obj[fieldName];
|
||||
}
|
||||
});
|
||||
|
||||
if (obj.searchSource) {
|
||||
body.searchSourceJSON = JSON.stringify(obj.searchSource);
|
||||
}
|
||||
|
||||
return docSource.doIndex(body).then(function (id) {
|
||||
obj.id = id;
|
||||
return id;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy this object
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.destroy = function () {
|
||||
docSource.cancelPending();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return SavedObject;
|
||||
});
|
||||
});
|
|
@ -27,7 +27,7 @@ define(function (require) {
|
|||
|
||||
function checkForES() {
|
||||
notify.lifecycle('es check');
|
||||
return es.ping()
|
||||
return es.ping({ requestTimeout: 2000 })
|
||||
.catch(function () {
|
||||
throw new Error('Unable to connect to Elasticsearch at "' + configFile.elasticsearch + '"');
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue