kibana-123
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
387 lines (331 loc) • 11 kB
JavaScript
import _ from 'lodash';
import errors from 'ui/errors';
import angular from 'angular';
import getComputedFields from 'ui/index_patterns/_get_computed_fields';
import formatHit from 'ui/index_patterns/_format_hit';
import RegistryFieldFormatsProvider from 'ui/registry/field_formats';
import IndexPatternsGetIdsProvider from 'ui/index_patterns/_get_ids';
import IndexPatternsMapperProvider from 'ui/index_patterns/_mapper';
import IndexPatternsIntervalsProvider from 'ui/index_patterns/_intervals';
import DocSourceProvider from 'ui/courier/data_source/admin_doc_source';
import UtilsMappingSetupProvider from 'ui/utils/mapping_setup';
import IndexPatternsFieldListProvider from 'ui/index_patterns/_field_list';
import IndexPatternsFlattenHitProvider from 'ui/index_patterns/_flatten_hit';
import IndexPatternsCalculateIndicesProvider from 'ui/index_patterns/_calculate_indices';
import IndexPatternsPatternCacheProvider from 'ui/index_patterns/_pattern_cache';
export default function IndexPatternFactory(Private, Notifier, config, kbnIndex, Promise, safeConfirm) {
const fieldformats = Private(RegistryFieldFormatsProvider);
const getIds = Private(IndexPatternsGetIdsProvider);
const mapper = Private(IndexPatternsMapperProvider);
const intervals = Private(IndexPatternsIntervalsProvider);
const DocSource = Private(DocSourceProvider);
const mappingSetup = Private(UtilsMappingSetupProvider);
const FieldList = Private(IndexPatternsFieldListProvider);
const flattenHit = Private(IndexPatternsFlattenHitProvider);
const calculateIndices = Private(IndexPatternsCalculateIndicesProvider);
const patternCache = Private(IndexPatternsPatternCacheProvider);
const type = 'index-pattern';
const notify = new Notifier();
const configWatchers = new WeakMap();
const docSources = new WeakMap();
const getRoutes = () => ({
edit: '/management/kibana/indices/{{id}}',
addField: '/management/kibana/indices/{{id}}/create-field',
indexedFields: '/management/kibana/indices/{{id}}?_a=(tab:indexedFields)',
scriptedFields: '/management/kibana/indices/{{id}}?_a=(tab:scriptedFields)',
sourceFilters: '/management/kibana/indices/{{id}}?_a=(tab:sourceFilters)'
});
const mapping = mappingSetup.expandShorthand({
title: 'string',
timeFieldName: 'string',
notExpandable: 'boolean',
intervalName: 'string',
fields: 'json',
sourceFilters: 'json',
fieldFormatMap: {
type: 'string',
_serialize(map = {}) {
const serialized = _.transform(map, serialize);
return _.isEmpty(serialized) ? undefined : angular.toJson(serialized);
},
_deserialize(map = '{}') {
return _.mapValues(angular.fromJson(map), deserialize);
}
}
});
function serialize(flat, format, field) {
if (format) {
flat[field] = format;
}
}
function deserialize(mapping) {
const FieldFormat = fieldformats.byId[mapping.id];
return FieldFormat && new FieldFormat(mapping.params);
}
function updateFromElasticSearch(indexPattern, response) {
if (!response.found) {
throw new errors.SavedObjectNotFound(type, indexPattern.id);
}
_.forOwn(mapping, (fieldMapping, name) => {
if (!fieldMapping._deserialize) {
return;
}
response._source[name] = fieldMapping._deserialize(
response._source[name], response, name, fieldMapping
);
});
// give index pattern all of the values in _source
_.assign(indexPattern, response._source);
const promise = indexFields(indexPattern);
// any time index pattern in ES is updated, update index pattern object
docSources
.get(indexPattern)
.onUpdate()
.then(response => updateFromElasticSearch(indexPattern, response), notify.fatal);
return promise;
}
function containsFieldCapabilities(fields) {
return _.any(fields, (field) => {
return _.has(field, 'aggregatable') && _.has(field, 'searchable');
});
}
function indexFields(indexPattern) {
let promise = Promise.resolve();
if (!indexPattern.id) {
return promise;
}
if (!indexPattern.fields || !containsFieldCapabilities(indexPattern.fields)) {
promise = indexPattern.refreshFields();
}
return promise.then(() => {initFields(indexPattern);});
}
function setId(indexPattern, id) {
indexPattern.id = id;
return id;
}
function watch(indexPattern) {
if (configWatchers.has(indexPattern)) {
return;
}
const unwatch = config.watchAll(() => {
if (indexPattern.fields) {
initFields(indexPattern); // re-init fields when config changes, but only if we already had fields
}
});
configWatchers.set(indexPattern, { unwatch });
}
function unwatch(indexPattern) {
if (!configWatchers.has(indexPattern)) {
return;
}
configWatchers.get(indexPattern).unwatch();
configWatchers.delete(indexPattern);
}
function initFields(indexPattern, input) {
const oldValue = indexPattern.fields;
const newValue = input || oldValue || [];
indexPattern.fields = new FieldList(indexPattern, newValue);
}
function fetchFields(indexPattern) {
return mapper
.getFieldsForIndexPattern(indexPattern, true)
.then(fields => {
const scripted = indexPattern.getScriptedFields();
const all = fields.concat(scripted);
initFields(indexPattern, all);
});
}
class IndexPattern {
constructor(id) {
setId(this, id);
docSources.set(this, new DocSource());
this.metaFields = config.get('metaFields');
this.getComputedFields = getComputedFields.bind(this);
this.flattenHit = flattenHit(this);
this.formatHit = formatHit(this, fieldformats.getDefaultInstance('string'));
this.formatField = this.formatHit.formatField;
}
get routes() {
return getRoutes();
}
init() {
docSources
.get(this)
.index(kbnIndex)
.type(type)
.id(this.id);
watch(this);
return mappingSetup
.isDefined(type)
.then(defined => {
if (defined) {
return true;
}
return mappingSetup.setup(type, mapping);
})
.then(() => {
if (!this.id) {
return; // no id === no elasticsearch document
}
return docSources.get(this)
.fetch()
.then(response => updateFromElasticSearch(this, response));
})
.then(() => this);
}
// Get the source filtering configuration for that index.
getSourceFiltering() {
return {
excludes: this.sourceFilters && this.sourceFilters.map(filter => filter.value) || []
};
}
addScriptedField(name, script, type = 'string', lang) {
const scriptedFields = this.getScriptedFields();
const names = _.pluck(scriptedFields, 'name');
if (_.contains(names, name)) {
throw new errors.DuplicateField(name);
}
this.fields.push({
name: name,
script: script,
type: type,
scripted: true,
lang: lang
});
this.save();
}
removeScriptedField(name) {
const fieldIndex = _.findIndex(this.fields, {
name: name,
scripted: true
});
this.fields.splice(fieldIndex, 1);
this.save();
}
popularizeField(fieldName, unit = 1) {
const field = _.get(this, ['fields', 'byName', fieldName]);
if (!field) {
return;
}
const count = Math.max((field.count || 0) + unit, 0);
if (field.count === count) {
return;
}
field.count = count;
this.save();
}
getNonScriptedFields() {
return _.where(this.fields, { scripted: false });
}
getScriptedFields() {
return _.where(this.fields, { scripted: true });
}
getInterval() {
return this.intervalName && _.find(intervals, { name: this.intervalName });
}
toIndexList(start, stop, sortDirection) {
return this
.toDetailedIndexList(start, stop, sortDirection)
.then(detailedIndices => {
if (!_.isArray(detailedIndices)) {
return detailedIndices.index;
}
return _.pluck(detailedIndices, 'index');
});
}
toDetailedIndexList(start, stop, sortDirection) {
return Promise.resolve().then(() => {
const interval = this.getInterval();
if (interval) {
return intervals.toIndexList(
this.id, interval, start, stop, sortDirection
);
}
if (this.isWildcard() && this.hasTimeField() && this.canExpandIndices()) {
return calculateIndices(
this.id, this.timeFieldName, start, stop, sortDirection
);
}
return {
index: this.id,
min: -Infinity,
max: Infinity
};
});
}
canExpandIndices() {
return !this.notExpandable;
}
hasTimeField() {
return !!(this.timeFieldName && this.fields.byName[this.timeFieldName]);
}
isWildcard() {
return _.includes(this.id, '*');
}
prepBody() {
const body = {};
// serialize json fields
_.forOwn(mapping, (fieldMapping, fieldName) => {
if (this[fieldName] != null) {
body[fieldName] = (fieldMapping._serialize)
? fieldMapping._serialize(this[fieldName])
: this[fieldName];
}
});
// ensure that the docSource has the current this.id
docSources.get(this).id(this.id);
// clear the indexPattern list cache
getIds.clearCache();
return body;
}
create() {
const body = this.prepBody();
return docSources.get(this)
.doCreate(body)
.then(id => setId(this, id))
.catch(err => {
if (_.get(err, 'origError.status') !== 409) {
return Promise.resolve(false);
}
const confirmMessage = 'Are you sure you want to overwrite this?';
return safeConfirm(confirmMessage)
.then(() => Promise
.try(() => {
const cached = patternCache.get(this.id);
if (cached) {
return cached.then(pattern => pattern.destroy());
}
})
.then(() => docSources.get(this).doIndex(body))
.then(id => setId(this, id)),
_.constant(false) // if the user doesn't overwrite, resolve with false
);
});
}
save() {
const body = this.prepBody();
return docSources.get(this)
.doIndex(body)
.then(id => setId(this, id));
}
refreshFields() {
return mapper
.clearCache(this)
.then(() => fetchFields(this))
.then(() => this.save());
}
toJSON() {
return this.id;
}
toString() {
return '' + this.toJSON();
}
destroy() {
unwatch(this);
patternCache.clear(this.id);
docSources.get(this).destroy();
docSources.delete(this);
}
}
return IndexPattern;
};