UNPKG

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
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; };