@spalger/kibana
Version:
Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic
298 lines (248 loc) • 8.93 kB
JavaScript
define(function (require) {
return function SavedObjectFactory(es, kbnIndex, Promise, Private, Notifier, indexPatterns) {
var angular = require('angular');
var errors = require('ui/errors');
var _ = require('lodash');
var slugifyId = require('ui/utils/slugify_id');
var DocSource = Private(require('ui/courier/data_source/doc_source'));
var SearchSource = Private(require('ui/courier/data_source/search_source'));
var mappingSetup = Private(require('ui/utils/mapping_setup'));
function SavedObject(config) {
if (!_.isObject(config)) config = {};
// save an easy reference to this
var self = this;
/************
* Initialize config vars
************/
// the doc which is used to store this object
var docSource = new DocSource();
// type name for this object, used as the ES-type
var type = config.type;
// Create a notifier for sending alerts
var notify = new Notifier({
location: 'Saved ' + type
});
// mapping definition for the fields that this object will expose
var mapping = mappingSetup.expandShorthand(config.mapping);
// default field values, assigned when the source is loaded
var defaults = config.defaults || {};
var afterESResp = config.afterESResp || _.noop;
var customInit = config.init || _.noop;
// optional search source which this object configures
self.searchSource = config.searchSource && new SearchSource();
// the id of the document
self.id = config.id || void 0;
self.defaults = config.defaults;
/**
* Asynchronously initialize this object - will only run
* once even if called multiple times.
*
* @return {Promise}
* @resolved {SavedObject}
*/
self.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(kbnIndex)
.type(type)
.id(self.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;
mapping.kibanaSavedObjectMeta = {
properties: {
// 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 (!self.id) {
// just assign the defaults and be done
_.assign(self, defaults);
return hydrateIndexPattern().then(function () {
return afterESResp.call(self);
});
}
// fetch the object from ES
return docSource.fetch()
.then(self.applyESResp);
})
.then(function () {
return customInit.call(self);
})
.then(function () {
// return our obj as the result of init()
return self;
});
});
self.applyESResp = function (resp) {
self._source = _.cloneDeep(resp._source);
if (resp.found != null && !resp.found) throw new errors.SavedObjectNotFound(type, self.id);
var meta = resp._source.kibanaSavedObjectMeta || {};
delete resp._source.kibanaSavedObjectMeta;
if (!config.indexPattern && self._source.indexPattern) {
config.indexPattern = self._source.indexPattern;
delete self._source.indexPattern;
}
// assign the defaults to the response
_.defaults(self._source, defaults);
// transform the source using _deserializers
_.forOwn(mapping, function ittr(fieldMapping, fieldName) {
if (fieldMapping._deserialize) {
self._source[fieldName] = fieldMapping._deserialize(self._source[fieldName], resp, fieldName, fieldMapping);
}
});
// Give obj all of the values in _source.fields
_.assign(self, self._source);
return Promise.try(function () {
parseSearchSource(meta.searchSourceJSON);
})
.then(hydrateIndexPattern)
.then(function () {
return Promise.cast(afterESResp.call(self, resp));
})
.then(function () {
// Any time obj is updated, re-call applyESResp
docSource.onUpdate().then(self.applyESResp, notify.fatal);
});
};
function parseSearchSource(searchSourceJson) {
if (!self.searchSource) return;
// if we have a searchSource, set its state based on the searchSourceJSON field
var state;
try {
state = JSON.parse(searchSourceJson);
} catch (e) {
state = {};
}
var oldState = self.searchSource.toJSON();
var fnProps = _.transform(oldState, function (dynamic, val, name) {
if (_.isFunction(val)) dynamic[name] = val;
}, {});
self.searchSource.set(_.defaults(state, fnProps));
}
/**
* After creation or fetching from ES, ensure that the searchSources index indexPattern
* is an bonafide IndexPattern object.
*
* @return {[type]} [description]
*/
function hydrateIndexPattern() {
return Promise.try(function () {
if (self.searchSource) {
var index = config.indexPattern || self.searchSource.getOwn('index');
if (!index) return;
if (config.clearSavedIndexPattern) {
self.searchSource.set('index', undefined);
return;
}
if (!(index instanceof indexPatterns.IndexPattern)) {
index = indexPatterns.get(index);
}
return Promise.resolve(index).then(function (indexPattern) {
self.searchSource.set('index', indexPattern);
});
}
});
}
/**
* Serialize this object
*
* @return {Object}
*/
self.serialize = function () {
var body = {};
_.forOwn(mapping, function (fieldMapping, fieldName) {
if (self[fieldName] != null) {
body[fieldName] = (fieldMapping._serialize)
? fieldMapping._serialize(self[fieldName])
: self[fieldName];
}
});
if (self.searchSource) {
body.kibanaSavedObjectMeta = {
searchSourceJSON: angular.toJson(_.omit(self.searchSource.toJSON(), ['sort', 'size']))
};
}
return body;
};
/**
* Save this object
*
* @return {Promise}
* @resolved {String} - The id of the doc
*/
self.save = function () {
var body = self.serialize();
// Slugify the object id
self.id = slugifyId(self.id);
// ensure that the docSource has the current self.id
docSource.id(self.id);
// index the document
return self.saveSource(body);
};
self.saveSource = function (source) {
var finish = function (id) {
self.id = id;
return es.indices.refresh({
index: kbnIndex
})
.then(function () {
return self.id;
});
};
return docSource.doCreate(source)
.then(finish)
.catch(function (err) {
// record exists, confirm overwriting
if (_.get(err, 'origError.status') === 409) {
var confirmMessage = 'Are you sure you want to overwrite ' + self.title + '?';
if (window.confirm(confirmMessage)) { // eslint-disable-line no-alert
return docSource.doIndex(source).then(finish);
}
// if the user doesn't overwrite record, just swallow the error
return;
}
return Promise.reject(err);
});
};
/**
* Destroy this object
*
* @return {undefined}
*/
self.destroy = function () {
docSource.cancelQueued();
if (self.searchSource) {
self.searchSource.cancelQueued();
}
};
/**
* Delete this object from Elasticsearch
* @return {promise}
*/
self.delete = function () {
return es.delete({
index: kbnIndex,
type: type,
id: this.id
}).then(function () {
return es.indices.refresh({
index: kbnIndex
});
});
};
}
return SavedObject;
};
});