mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
index_patterns now can have patterned index names
This commit is contained in:
parent
45b0ab75aa
commit
f6e1bafa31
48 changed files with 1129 additions and 719 deletions
|
@ -14,6 +14,7 @@ define(function (require) {
|
|||
require('directives/fixed_scroll');
|
||||
require('filters/moment');
|
||||
require('courier/courier');
|
||||
require('index_patterns/index_patterns');
|
||||
require('state_management/app_state');
|
||||
require('services/timefilter');
|
||||
|
||||
|
@ -22,7 +23,8 @@ define(function (require) {
|
|||
var app = require('modules').get('app/discover', [
|
||||
'kibana/services',
|
||||
'kibana/notify',
|
||||
'kibana/courier'
|
||||
'kibana/courier',
|
||||
'kibana/index_patterns'
|
||||
]);
|
||||
|
||||
require('routes')
|
||||
|
@ -34,9 +36,9 @@ define(function (require) {
|
|||
return savedSearches.get($route.current.params.id)
|
||||
.catch(courier.redirectWhenMissing('/discover'));
|
||||
},
|
||||
indexPatternList: function (courier) {
|
||||
return courier.indexPatterns.getIds()
|
||||
.then(courier.indexPatterns.ensureSome());
|
||||
indexPatternList: function (indexPatterns) {
|
||||
return indexPatterns.getIds()
|
||||
.then(indexPatterns.ensureSome());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -102,9 +104,9 @@ define(function (require) {
|
|||
$scope.fields = null;
|
||||
|
||||
var init = _.once(function () {
|
||||
return setFields()
|
||||
return updateDataSource()
|
||||
.then(function () {
|
||||
updateDataSource();
|
||||
setFields();
|
||||
|
||||
// state fields that shouldn't trigger a fetch when changed
|
||||
var ignoreStateChanges = ['columns'];
|
||||
|
@ -131,6 +133,10 @@ define(function (require) {
|
|||
if (!angular.equals(sort, currentSort)) $scope.fetch();
|
||||
});
|
||||
|
||||
$scope.$watch('opts.timefield', function (timefield) {
|
||||
timefilter.enabled(!!timefield);
|
||||
});
|
||||
|
||||
searchSource.onError().then(function searchError(err) {
|
||||
console.log(err);
|
||||
notify.error('An error occured with your request. Reset your inputs and try again.');
|
||||
|
@ -164,27 +170,29 @@ define(function (require) {
|
|||
});
|
||||
|
||||
$scope.opts.saveDataSource = function () {
|
||||
updateDataSource();
|
||||
savedSearch.id = savedSearch.title;
|
||||
|
||||
savedSearch.save()
|
||||
return updateDataSource()
|
||||
.then(function () {
|
||||
notify.info('Saved Data Source "' + savedSearch.title + '"');
|
||||
if (savedSearch.id !== $route.current.params.id) {
|
||||
$location.url(globalState.writeToUrl('/discover/' + savedSearch.id));
|
||||
}
|
||||
}, notify.error);
|
||||
savedSearch.id = savedSearch.title;
|
||||
|
||||
return savedSearch.save()
|
||||
.then(function () {
|
||||
notify.info('Saved Data Source "' + savedSearch.title + '"');
|
||||
if (savedSearch.id !== $route.current.params.id) {
|
||||
$location.url(globalState.writeToUrl('/discover/' + savedSearch.id));
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(notify.error);
|
||||
};
|
||||
|
||||
$scope.fetch = function () {
|
||||
setupVisualization().then(function () {
|
||||
if ($scope.opts.timefield) timefilter.enabled(true);
|
||||
|
||||
updateDataSource();
|
||||
setupVisualization()
|
||||
.then(updateDataSource)
|
||||
.then(function () {
|
||||
$state.commit();
|
||||
|
||||
courier.fetch();
|
||||
}, notify.error);
|
||||
})
|
||||
.catch(notify.error);
|
||||
};
|
||||
|
||||
$scope.toggleConfig = function () {
|
||||
|
@ -233,16 +241,29 @@ define(function (require) {
|
|||
}
|
||||
});
|
||||
|
||||
if ($scope.opts.index !== searchSource.get('index')) {
|
||||
// set the index on the savedSearch
|
||||
searchSource.index($scope.opts.index);
|
||||
|
||||
$state.index = $scope.opts.index;
|
||||
delete $scope.fields;
|
||||
delete $scope.columns;
|
||||
|
||||
setFields();
|
||||
// get the current indexPattern
|
||||
var indexPattern = searchSource.get('index');
|
||||
// if indexPattern exists, but $scope.opts.index doesn't, or the opposite, or if indexPattern's id
|
||||
// is not equal to the $scope.opts.index then either clean or
|
||||
if (
|
||||
Boolean($scope.opts.index) !== Boolean(indexPattern)
|
||||
|| (indexPattern && indexPattern.id) !== $scope.opts.index
|
||||
) {
|
||||
$state.index = $scope.opts.index = $scope.opts.index || config.get('defaultIndex');
|
||||
indexPattern = courier.indexPatterns.get($scope.opts.index);
|
||||
}
|
||||
|
||||
return Promise.cast(indexPattern)
|
||||
.then(function (indexPattern) {
|
||||
if (indexPattern !== searchSource.get('index')) {
|
||||
// set the index on the savedSearch
|
||||
searchSource.index(indexPattern);
|
||||
delete $scope.fields;
|
||||
delete $scope.columns;
|
||||
|
||||
setFields();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This is a hacky optimization for comparing the contents of a large array to a short one.
|
||||
|
@ -255,46 +276,44 @@ define(function (require) {
|
|||
}
|
||||
|
||||
function setFields() {
|
||||
return courier.getFieldsFor($scope.opts.index)
|
||||
.then(function (rawFields) {
|
||||
var currentState = _.transform($scope.fields || [], function (current, field) {
|
||||
current[field.name] = {
|
||||
display: field.display
|
||||
};
|
||||
}, {});
|
||||
var indexPattern = searchSource.get('index');
|
||||
var currentState = _.transform($scope.fields || [], function (current, field) {
|
||||
current[field.name] = {
|
||||
display: field.display
|
||||
};
|
||||
}, {});
|
||||
|
||||
if (!rawFields) return;
|
||||
if (!indexPattern) return;
|
||||
|
||||
var columnObjects = arrayToKeys($scope.state.columns);
|
||||
var columnObjects = arrayToKeys($scope.state.columns);
|
||||
|
||||
$scope.fields = [];
|
||||
$scope.fieldsByName = {};
|
||||
$scope.formatsByName = {};
|
||||
$scope.state.columns = $scope.state.columns || [];
|
||||
$scope.fields = [];
|
||||
$scope.fieldsByName = {};
|
||||
$scope.formatsByName = {};
|
||||
$scope.state.columns = $scope.state.columns || [];
|
||||
|
||||
// Inject source into list;
|
||||
$scope.fields.push({name: '_source', type: 'source', display: false});
|
||||
// Inject source into list;
|
||||
$scope.fields.push({name: '_source', type: 'source', display: false});
|
||||
|
||||
_.sortBy(rawFields, 'name').forEach(function (field) {
|
||||
_.defaults(field, currentState[field.name]);
|
||||
// clone the field and add it's display prop
|
||||
var clone = _.assign({}, field, { display: columnObjects[name] || false });
|
||||
$scope.fields.push(clone);
|
||||
$scope.fieldsByName[field.name] = clone;
|
||||
$scope.formatsByName[field.name] = field.format;
|
||||
});
|
||||
_.sortBy(indexPattern.fields, 'name').forEach(function (field) {
|
||||
_.defaults(field, currentState[field.name]);
|
||||
// clone the field and add it's display prop
|
||||
var clone = _.assign({}, field, { display: columnObjects[name] || false });
|
||||
$scope.fields.push(clone);
|
||||
$scope.fieldsByName[field.name] = clone;
|
||||
$scope.formatsByName[field.name] = field.format;
|
||||
});
|
||||
|
||||
// TODO: timefield should be associated with the index pattern, this is a hack
|
||||
// to pick the first date field and use it.
|
||||
var timefields = _.find($scope.fields, {type: 'date'});
|
||||
if (!!timefields) {
|
||||
$scope.opts.timefield = timefields.name;
|
||||
} else {
|
||||
delete $scope.opts.timefield;
|
||||
}
|
||||
// TODO: timefield should be associated with the index pattern, this is a hack
|
||||
// to pick the first date field and use it.
|
||||
var timefields = _.find($scope.fields, {type: 'date'});
|
||||
if (!!timefields) {
|
||||
$scope.opts.timefield = timefields.name;
|
||||
} else {
|
||||
delete $scope.opts.timefield;
|
||||
}
|
||||
|
||||
refreshColumns();
|
||||
}, notify.error);
|
||||
refreshColumns();
|
||||
}
|
||||
|
||||
// TODO: On array fields, negating does not negate the combination, rather all terms
|
||||
|
@ -366,46 +385,59 @@ define(function (require) {
|
|||
return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
};
|
||||
|
||||
var loadingVis;
|
||||
var setupVisualization = function () {
|
||||
return new Promise(function (resolve, reject) {
|
||||
// we shouldn't have a vis, delete it
|
||||
if (!$scope.opts.timefield && $scope.vis) delete $scope.vis;
|
||||
// we shouldn't have one, or already do, return whatever we already have
|
||||
if (!$scope.opts.timefield || $scope.vis) return resolve($scope.vis);
|
||||
if (loadingVis) return loadingVis;
|
||||
|
||||
// set the scopes vis property to the AdhocVis so that we know not to re-init
|
||||
$scope.vis = new AdhocVis({
|
||||
searchSource: searchSource,
|
||||
type: 'histogram',
|
||||
listeners: {
|
||||
onClick: function (e) {
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
config: {
|
||||
metric: {
|
||||
configs: [{
|
||||
agg: 'count',
|
||||
}]
|
||||
},
|
||||
segment: {
|
||||
configs: [{
|
||||
agg: 'date_histogram',
|
||||
field: $scope.opts.timefield,
|
||||
min_doc_count: 0,
|
||||
}]
|
||||
},
|
||||
group: { configs: [] },
|
||||
split: { configs: [] },
|
||||
// we shouldn't have a vis, delete it
|
||||
if (!$scope.opts.timefield && $scope.vis) delete $scope.vis;
|
||||
// we shouldn't have one, or already do, return whatever we already have
|
||||
if (!$scope.opts.timefield || $scope.vis) return Promise.resolve($scope.vis);
|
||||
|
||||
var vis = new AdhocVis({
|
||||
searchSource: searchSource,
|
||||
type: 'histogram',
|
||||
listeners: {
|
||||
onClick: function (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
// once the visualization is ready, resolve the promise with the vis
|
||||
$scope.$on('ready:vis', function () {
|
||||
// enable the source, but wait for the visualization to be ready before running
|
||||
resolve($scope.vis);
|
||||
});
|
||||
},
|
||||
config: {
|
||||
metric: {
|
||||
configs: [{
|
||||
agg: 'count',
|
||||
}]
|
||||
},
|
||||
segment: {
|
||||
configs: [{
|
||||
agg: 'date_histogram',
|
||||
field: $scope.opts.timefield,
|
||||
min_doc_count: 0,
|
||||
}]
|
||||
},
|
||||
group: { configs: [] },
|
||||
split: { configs: [] },
|
||||
}
|
||||
});
|
||||
|
||||
// stash this promise so that other calls to setupVisualization will have to wait
|
||||
loadingVis = vis.init()
|
||||
.then(function () {
|
||||
// expose the vis so that the visualize directive can get started
|
||||
$scope.vis = vis;
|
||||
|
||||
// wait for visualize directive to emit that it's ready before resolving
|
||||
return new Promise(function (resolve) {
|
||||
$scope.$on('ready:vis', resolve);
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
// clear the loading flag
|
||||
loadingVis = null;
|
||||
return vis;
|
||||
});
|
||||
|
||||
return loadingVis;
|
||||
};
|
||||
|
||||
init();
|
||||
|
|
|
@ -14,6 +14,7 @@ define(function (require) {
|
|||
restrict: 'E',
|
||||
template: html,
|
||||
replace: true,
|
||||
require: '^discFieldChooser',
|
||||
link: function ($scope, $elem) {
|
||||
var detailsElem;
|
||||
var detailScope = $scope.$new();
|
||||
|
@ -24,6 +25,16 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
|
||||
$scope.toggleDisplay = function (field) {
|
||||
// inheritted param to fieldChooser
|
||||
$scope.toggle(field.name);
|
||||
|
||||
// we are now displaying the field, kill it's details
|
||||
if (field.details) {
|
||||
$scope.toggleDetails(field);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleDetails = function (field, recompute) {
|
||||
if (_.isUndefined(field.details) || recompute) {
|
||||
// This is inherited from fieldChooser
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<div class="discover-field-details">
|
||||
|
||||
<a ng-click="toggle(field.name)" bo-class="displayButton(field)" class="pull-right btn btn-default btn-xs discover-field-toggle">
|
||||
<a ng-click="toggleDisplay(field)" bo-class="displayButton(field)" class="pull-right btn btn-default btn-xs discover-field-toggle">
|
||||
<i class="fa fa-table"></i>
|
||||
</a>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ define(function (require) {
|
|||
'kibana/courier'
|
||||
]);
|
||||
|
||||
module.factory('SavedSearch', function (configFile, courier, Promise) {
|
||||
module.factory('SavedSearch', function (courier, indexPatterns) {
|
||||
function SavedSearch(id) {
|
||||
courier.SavedObject.call(this, {
|
||||
type: 'search',
|
||||
|
@ -33,7 +33,7 @@ define(function (require) {
|
|||
afterESResp: function () {
|
||||
var index = this.searchSource.get('index');
|
||||
if (typeof index === 'string') {
|
||||
this.searchSource.index(courier.indexPatterns.get(index));
|
||||
this.searchSource.index(indexPatterns.get(index));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,26 +1,141 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
var errors = require('errors');
|
||||
|
||||
require('routes').when('/settings/indices/', {
|
||||
template: require('text!../../partials/indices/create.html')
|
||||
});
|
||||
|
||||
require('modules').get('app/settings')
|
||||
.controller('kbnSettingsIndicesCreate', function ($scope, courier, $location, Notifier, Private) {
|
||||
.controller('kbnSettingsIndicesCreate', function ($scope, $location, Notifier, Private, indexPatterns, es) {
|
||||
var notify = new Notifier();
|
||||
var refreshKibanaIndex = Private(require('./_refresh_kibana_index'));
|
||||
var MissingIndices = courier.indexPatterns.errors.MissingIndices;
|
||||
var MissingIndices = errors.IndexPatternMissingIndices;
|
||||
var intervals = indexPatterns.intervals;
|
||||
|
||||
$scope.create = function () {
|
||||
// this and child scopes will write pattern vars here
|
||||
var index = $scope.index = {
|
||||
// set in updateDefaultIndexName watcher
|
||||
name: null,
|
||||
defaultName: null,
|
||||
|
||||
isTimeBased: false,
|
||||
nameIsPattern: false,
|
||||
sampleCount: 5,
|
||||
nameIntervalOptions: intervals
|
||||
};
|
||||
|
||||
index.nameInterval = _.find(index.nameIntervalOptions, { name: 'daily' });
|
||||
index.timeField = _.find(index.nameIntervalOptions, { name: 'field' });
|
||||
|
||||
var updateSamples = function () {
|
||||
index.samples = null;
|
||||
index.existing = null;
|
||||
index.patternErrors = [];
|
||||
|
||||
if (!index.nameInterval || !index.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// replace anything that is outside of brackets with a *
|
||||
var wildcard = indexPatterns.patternToWildcard(index.name);
|
||||
es.indices.getAliases({
|
||||
index: wildcard
|
||||
})
|
||||
.then(function (resp) {
|
||||
var all = Object.keys(resp);
|
||||
var matches = all.filter(function (existingIndex) {
|
||||
var parsed = moment(existingIndex, index.name);
|
||||
return existingIndex === parsed.format(index.name);
|
||||
});
|
||||
|
||||
if (all.length) {
|
||||
index.existing = {
|
||||
class: all.length === matches.length ? 'success' : 'warning',
|
||||
all: all,
|
||||
matches: matches,
|
||||
matchPercent: Math.round((matches.length / all.length) * 100) + '%',
|
||||
failures: _.difference(all, matches)
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
index.patternErrors.push('Pattern does not match any existing indices');
|
||||
var radius = Math.round(index.sampleCount / 2);
|
||||
var samples = intervals.toIndexList(index.name, index.nameInterval, -radius, radius);
|
||||
|
||||
if (_.uniq(samples).length !== samples.length) {
|
||||
index.patternErrors.push('Invalid pattern, interval does not create unique index names');
|
||||
} else {
|
||||
index.samples = samples;
|
||||
}
|
||||
})
|
||||
.catch(notify.error);
|
||||
};
|
||||
|
||||
$scope.refreshFieldList = function () {
|
||||
index.dateFields = index.timeField = index.fetchFieldsError = null;
|
||||
|
||||
var useIndexList = index.isTimeBased && index.nameIsPattern;
|
||||
|
||||
// we don't have enough info to continue
|
||||
if (!index.name) {
|
||||
index.fetchFieldsError = 'Set an index name first';
|
||||
return;
|
||||
}
|
||||
|
||||
if (useIndexList && !index.nameInterval) {
|
||||
index.fetchFieldsError = 'Select the interval at which your indices are populated.';
|
||||
return;
|
||||
}
|
||||
|
||||
indexPatterns.mapper.clearCache(index.name)
|
||||
.then(function () {
|
||||
var pattern = index.name;
|
||||
|
||||
if (useIndexList) {
|
||||
// trick the mapper into thinking this is an indexPattern
|
||||
pattern = {
|
||||
id: index.name,
|
||||
toIndexList: function (to, from) {
|
||||
return intervals.toIndexList(index.name, index.nameInterval, to, from);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return indexPatterns.mapper.getFieldsForIndexPattern(pattern, true)
|
||||
.catch(function (err) {
|
||||
// TODO: we should probably display a message of some kind
|
||||
if (err instanceof MissingIndices) return [];
|
||||
throw err;
|
||||
});
|
||||
})
|
||||
.then(function (fields) {
|
||||
index.dateFields = fields.filter(function (field) {
|
||||
return field.type === 'date';
|
||||
});
|
||||
}, notify.fatal);
|
||||
};
|
||||
|
||||
$scope.createIndexPattern = function () {
|
||||
// get an empty indexPattern to start
|
||||
courier.indexPatterns.get()
|
||||
indexPatterns.get()
|
||||
.then(function (indexPattern) {
|
||||
// set both the id and title to the index pattern
|
||||
indexPattern.id = indexPattern.title = $scope.newIndexPattern;
|
||||
// set both the id and title to the index index
|
||||
indexPattern.id = indexPattern.title = index.name;
|
||||
if (index.isTimeBased) {
|
||||
indexPattern.timeFieldName = index.timeField.name;
|
||||
if (index.nameIsPattern) {
|
||||
indexPattern.intervalName = index.nameInterval.name;
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the fields
|
||||
return indexPattern.refreshFields()
|
||||
.then(refreshKibanaIndex)
|
||||
.then(function () {
|
||||
courier.indexPatterns.cache.clear(indexPattern.id);
|
||||
indexPatterns.cache.clear(indexPattern.id);
|
||||
$location.url('/settings/indices/' + indexPattern.id);
|
||||
});
|
||||
|
||||
|
@ -34,5 +149,30 @@ define(function (require) {
|
|||
else notify.fatal(err);
|
||||
});
|
||||
};
|
||||
|
||||
var updateDefaultIndexName = function () {
|
||||
var newDefault = index.nameIsPattern
|
||||
? '[logstash-]YYYY.MM.DD'
|
||||
: 'logstash-*';
|
||||
|
||||
if (index.name === index.defaultName) {
|
||||
index.name = index.defaultName = newDefault;
|
||||
} else {
|
||||
index.defaultName = newDefault;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.moreSamples = function (andUpdate) {
|
||||
index.sampleCount += 5;
|
||||
if (andUpdate) updateSamples();
|
||||
};
|
||||
|
||||
$scope.$watch('index.name', updateSamples);
|
||||
$scope.$watch('index.name', $scope.refreshFieldList);
|
||||
$scope.$watch('index.isTimeBased', $scope.refreshFieldList);
|
||||
$scope.$watch('index.nameIsPattern', updateDefaultIndexName);
|
||||
$scope.$watch('index.nameIsPattern', $scope.refreshFieldList);
|
||||
$scope.$watch('index.nameInterval', updateSamples);
|
||||
$scope.$watch('index.nameInterval', $scope.refreshFieldList);
|
||||
});
|
||||
});
|
|
@ -47,4 +47,4 @@ define(function (require) {
|
|||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
<th>Actions <kbn-info info="'Edit or restore the default value.'"></kbn-info></th>
|
||||
<th>Actions <kbn-info info="Edit or restore the default value."></kbn-info></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
@ -1,21 +1,124 @@
|
|||
<kbn-settings-app section="indices">
|
||||
<kbn-settings-indices>
|
||||
<div ng-controller="kbnSettingsIndicesCreate">
|
||||
<div ng-controller="kbnSettingsIndicesCreate" class="kbn-settings-indices-create">
|
||||
<div class="page-header">
|
||||
<h1>Configure an index pattern</h1>
|
||||
In order to use Kibana you must configure at least one index pattern. Index patterns are
|
||||
used to identify the Elasticsearch index to run search and analytics against. They are also
|
||||
used to configure fields.
|
||||
</div>
|
||||
<form name="form" role="form" class="well" ng-submit="create()">
|
||||
<div class="form-group">
|
||||
<label>Index Name <small>or pattern</small></label>
|
||||
<input type="text" class="form-control" placeholder="Enter index name"
|
||||
ng-model="newIndexPattern" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"
|
||||
ng-disabled="form.$invalid">Create</button>
|
||||
</form>
|
||||
<div class="container">
|
||||
<form name="form" role="form" class="well" ng-submit="createIndexPattern()">
|
||||
<div class="form-group time-and-pattern">
|
||||
<label>
|
||||
<input
|
||||
ng-model="index.isTimeBased"
|
||||
type="checkbox">
|
||||
Index contains time-based events
|
||||
</label>
|
||||
<br>
|
||||
<label ng-if="index.isTimeBased">
|
||||
<input ng-model="index.nameIsPattern" type="checkbox">
|
||||
Use event times to create index names
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
Index Name
|
||||
or Pattern —
|
||||
<small><a href="http://momentjs.com/docs/#/displaying/format/">docs</a></small>
|
||||
</label>
|
||||
<input
|
||||
ng-model="index.name"
|
||||
ng-attr-placeholder="{{index.defaultName}}"
|
||||
ng-model-options="{ updateOn: 'default blur', debounce: {'default': 2500, 'blur': 0} }"
|
||||
required
|
||||
type="text"
|
||||
class="form-control">
|
||||
</div>
|
||||
<section ng-if="index.isTimeBased && index.nameIsPattern">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
Index Pattern Interval
|
||||
<kbn-info info="The interval at which index names rotate."></kbn-info>
|
||||
</label>
|
||||
<select
|
||||
required
|
||||
ng-options="opt.display for opt in index.nameIntervalOptions"
|
||||
ng-model="index.nameInterval"
|
||||
class="form-control">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger" ng-repeat="err in index.patternErrors">{{err}}</div>
|
||||
|
||||
<div class="alert alert-info" ng-if="index.samples">
|
||||
Sample Index Names
|
||||
<ul>
|
||||
<li ng-repeat="sample in index.samples">{{sample}}</li>
|
||||
</ul>
|
||||
<a ng-click="moreSamples(true)" class="alert-link">more</a>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-{{index.existing.class}}" ng-if="index.existing">
|
||||
Pattern matches {{index.existing.matchPercent}} of similar indices
|
||||
<ul>
|
||||
<li ng-repeat="match in index.existing.matches | limitTo: index.sampleCount">{{match}}</li>
|
||||
</ul>
|
||||
<a
|
||||
ng-if="index.sampleCount < index.existing.matches.length"
|
||||
ng-click="moreSamples()"
|
||||
class="alert-link">
|
||||
more
|
||||
</a>
|
||||
</div>
|
||||
<div class="alert alert-danger" ng-if="index.existing.failures.length">
|
||||
Indices that did not match:
|
||||
<ul>
|
||||
<li ng-repeat="match in index.existing.failures | limitTo: index.sampleCount">{{match}}</li>
|
||||
</ul>
|
||||
<a
|
||||
ng-if="index.sampleCount < index.existing.matches.length"
|
||||
ng-click="moreSamples()"
|
||||
class="alert-link">
|
||||
more
|
||||
</a>
|
||||
</section>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
<div class="form-group" ng-if="index.isTimeBased">
|
||||
<label>
|
||||
Time-Field Name
|
||||
|
||||
<kbn-info info="This field will be use to filter events with the global time filter"></kbn-info>
|
||||
|
||||
<small>
|
||||
<a ng-click="refreshFieldList();"> refresh fields</a>
|
||||
</small>
|
||||
</label>
|
||||
<select
|
||||
required
|
||||
ng-if="!index.fetchFieldsError"
|
||||
ng-init="refreshFieldList();"
|
||||
ng-options="field.name for field in index.dateFields"
|
||||
ng-model="index.timeField"
|
||||
class="form-control">
|
||||
</select>
|
||||
<select ng-if="index.fetchFieldsError" class="form-control" disabled >
|
||||
<option>{{index.fetchFieldsError}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
ng-disabled="form.$invalid"
|
||||
type="submit"
|
||||
class="btn btn-success">
|
||||
Create
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</kbn-settings-indices>
|
||||
</kbn-settings-app>
|
|
@ -41,4 +41,8 @@ kbn-settings-advanced {
|
|||
color: @btn-success-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.kbn-settings-indices-create {
|
||||
.time-and-pattern > div {}
|
||||
}
|
|
@ -53,7 +53,8 @@ define(function (require) {
|
|||
vis.searchSource.onResults(function onResults(resp) {
|
||||
courier.indexPatterns.get(vis.searchSource.get('index'))
|
||||
.then(function (indexPattern) {
|
||||
chart.render(vis.buildChartDataFromResponse(indexPattern, resp));
|
||||
var chartData = vis.buildChartDataFromResponse(indexPattern, resp);
|
||||
chart.render(chartData);
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
}).catch(notify.fatal);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
name="custom interval">
|
||||
</td>
|
||||
<td ng-if="config.interval === 'customInterval'">
|
||||
<kbn-info info="'interval bewteen timestamp measurements, in milliseconds'"></kbn-info>
|
||||
<kbn-info info="interval bewteen timestamp measurements, in milliseconds"></kbn-info>
|
||||
</td> -->
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<div class="form-group" ng-if="config.agg && config.agg !== 'count'">
|
||||
<label for="field">
|
||||
Field to {{config.agg}}
|
||||
<kbn-info placement="right" info="'Field to use for the ' + metric.label"></kbn-info>
|
||||
<kbn-info placement="right" info="Field to use for the {{metric.label}}"></kbn-info>
|
||||
</label>
|
||||
<select
|
||||
class="form-control"
|
||||
|
|
|
@ -5,10 +5,9 @@ define(function (require) {
|
|||
var configCats = require('./_config_categories');
|
||||
|
||||
|
||||
module.factory('AdhocVis', function (courier, Private) {
|
||||
module.factory('AdhocVis', function (courier, Private, Promise) {
|
||||
var aggs = Private(require('./_aggs'));
|
||||
|
||||
|
||||
/**
|
||||
opts params:
|
||||
{
|
||||
|
@ -24,48 +23,66 @@ define(function (require) {
|
|||
|
||||
*/
|
||||
function AdhocVis(opts) {
|
||||
opts = opts || {};
|
||||
if (!_.isObject(opts)) throw new TypeError('options must be an object');
|
||||
|
||||
var vis = this;
|
||||
var params;
|
||||
|
||||
// Must get an object for this one
|
||||
if (!_.isObject(opts)) return;
|
||||
vis.init = _.once(function () {
|
||||
vis.typeName = opts.type || 'histogram';
|
||||
vis.params = _.cloneDeep(opts.params);
|
||||
|
||||
vis.typeName = opts.type || 'histogram';
|
||||
vis.params = _.cloneDeep(opts.params);
|
||||
vis.searchSource = opts.searchSource || courier.SavedObject.rootSearch();
|
||||
// give vis the properties of config
|
||||
_.assign(vis, opts.config);
|
||||
|
||||
// give this the properties of config
|
||||
_.merge(vis, opts.config);
|
||||
// also give it the on* interaction functions, if any
|
||||
_.assign(vis, opts.listeners);
|
||||
|
||||
// also give it the on* interaction functions, if any
|
||||
_.merge(vis, opts.listeners);
|
||||
vis._fillConfigsToMinimum();
|
||||
|
||||
// TODO: Should we abtract out the agg building stuff?
|
||||
vis.searchSource
|
||||
// reads the vis' config and write the agg to the searchSource
|
||||
.aggs(function () {
|
||||
// stores the config objects in queryDsl
|
||||
var dsl = {};
|
||||
// counter to ensure unique agg names
|
||||
var i = 0;
|
||||
// start at the root, but the current will move
|
||||
var current = dsl;
|
||||
// resolve the search source for this AdhocVis
|
||||
return Promise.cast((function () {
|
||||
if (opts.searchSource) return opts.searchSource;
|
||||
|
||||
// continue to nest the aggs under each other
|
||||
// writes to the dsl object
|
||||
vis.getConfig().forEach(function (config) {
|
||||
current.aggs = {};
|
||||
var key = '_agg_' + (i++);
|
||||
return courier.getRootSearch()
|
||||
.then(function (rootSearch) {
|
||||
var searchSource = courier.createSource('search');
|
||||
searchSource.inherits(rootSearch);
|
||||
return searchSource;
|
||||
});
|
||||
}()))
|
||||
.then(function (searchSource) {
|
||||
// TODO: Should we abtract out the agg building stuff?
|
||||
searchSource.aggs(function () {
|
||||
// stores the config objects in queryDsl
|
||||
var dsl = {};
|
||||
// counter to ensure unique agg names
|
||||
var i = 0;
|
||||
// start at the root, but the current will move
|
||||
var current = dsl;
|
||||
|
||||
var aggDsl = {};
|
||||
aggDsl[config.agg] = config.aggParams;
|
||||
// continue to nest the aggs under each other
|
||||
// writes to the dsl object
|
||||
vis.getConfig().forEach(function (config) {
|
||||
current.aggs = {};
|
||||
var key = '_agg_' + (i++);
|
||||
|
||||
current = current.aggs[key] = aggDsl;
|
||||
var aggDsl = {};
|
||||
aggDsl[config.agg] = config.aggParams;
|
||||
|
||||
current = current.aggs[key] = aggDsl;
|
||||
});
|
||||
|
||||
// set the dsl to the searchSource
|
||||
return dsl.aggs || {};
|
||||
});
|
||||
|
||||
// set the dsl to the searchSource
|
||||
return dsl.aggs || {};
|
||||
vis.searchSource = searchSource;
|
||||
|
||||
return vis;
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Should this be abstracted somewhere? Its a copy/paste from _saved_vis.js
|
||||
vis._fillConfigsToMinimum = function () {
|
||||
|
@ -81,8 +98,6 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
vis._fillConfigsToMinimum();
|
||||
|
||||
// Need these, but we have nothing to destroy for now;
|
||||
vis.destroy = function () {};
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ define(function (require) {
|
|||
}
|
||||
|
||||
var typeDef;
|
||||
var rootSearch = opts.parentSearchSource || courier.SavedObject.rootSearch();
|
||||
var defaultParent = opts.parentSearchSource;
|
||||
|
||||
courier.SavedObject.call(vis, {
|
||||
type: 'visualization',
|
||||
|
@ -74,17 +74,25 @@ define(function (require) {
|
|||
return savedSearches.get(vis.savedSearchId);
|
||||
}
|
||||
|
||||
if (relatedPattern) {
|
||||
// create a new search source that inherits from the parent and uses the indexPattern
|
||||
return Promise.resolve({
|
||||
searchSource: rootSearch.extend().index(relatedPattern)
|
||||
});
|
||||
}
|
||||
return courier.getRootSearch()
|
||||
.then(function (rootSearch) {
|
||||
|
||||
if (relatedPattern) {
|
||||
return courier.indexPatterns.get(relatedPattern)
|
||||
.then(function (indexPattern) {
|
||||
// create a new search source that inherits from the parent and uses the indexPattern
|
||||
return {
|
||||
searchSource: rootSearch.extend().index(indexPattern)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// default parent is the rootSearch, can be overridden (like in discover)
|
||||
// but we mimic the searchSource prop from saved objects here
|
||||
return {
|
||||
searchSource: rootSearch
|
||||
};
|
||||
|
||||
// default parent is the rootSearch, can be overridden (like in discover)
|
||||
// but we mimic the searchSource prop from saved objects here
|
||||
return Promise.resolve({
|
||||
searchSource: rootSearch
|
||||
});
|
||||
}());
|
||||
|
||||
|
@ -122,11 +130,6 @@ define(function (require) {
|
|||
|
||||
vis._fillConfigsToMinimum();
|
||||
|
||||
// get and cache the field list
|
||||
var index = vis.searchSource.get('index');
|
||||
if (index) return courier.getFieldsFor(index);
|
||||
})
|
||||
.then(function () {
|
||||
return vis;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var inherits = require('utils/inherits');
|
||||
|
||||
var canStack = (function () {
|
||||
var err = new Error();
|
||||
return !!err.stack;
|
||||
}());
|
||||
|
||||
return function () {
|
||||
var errors = this;
|
||||
|
||||
// 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 = '';
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
/**
|
||||
* Request Failure - When an entire mutli request fails
|
||||
* @param {Error} err - the Error that came back
|
||||
* @param {Object} resp - optional HTTP response
|
||||
*/
|
||||
errors.RequestFailure = function RequestFailure(err, resp) {
|
||||
CourierError.call(this,
|
||||
'Request to Elasticsearch failed: ' + JSON.stringify(resp || err.message),
|
||||
errors.RequestFailure);
|
||||
|
||||
this.origError = err;
|
||||
this.resp = resp;
|
||||
};
|
||||
inherits(errors.RequestFailure, CourierError);
|
||||
|
||||
/**
|
||||
* FetchFailure Error - when there is an error getting a doc or search within
|
||||
* a multi-response response body
|
||||
* @param {String} [msg] - An error message that will probably end up in a log.
|
||||
*/
|
||||
errors.FetchFailure = function FetchFailure(resp) {
|
||||
CourierError.call(this,
|
||||
'Failed to get the doc: ' + JSON.stringify(resp),
|
||||
errors.FetchFailure);
|
||||
|
||||
this.resp = resp;
|
||||
};
|
||||
inherits(errors.FetchFailure, CourierError);
|
||||
|
||||
|
||||
/**
|
||||
* A doc was re-indexed but it was out of date.
|
||||
* @param {Object} resp - The response from es (one of the multi-response responses).
|
||||
*/
|
||||
errors.VersionConflict = function VersionConflict(resp) {
|
||||
CourierError.call(this,
|
||||
'Failed to store document changes do to a version conflict.',
|
||||
errors.VersionConflict);
|
||||
|
||||
this.resp = resp;
|
||||
};
|
||||
inherits(errors.VersionConflict, CourierError);
|
||||
|
||||
|
||||
/**
|
||||
* there was a conflict storing a doc
|
||||
* @param {String} field - the fields which contains the conflict
|
||||
*/
|
||||
errors.MappingConflict = function MappingConflict(field) {
|
||||
CourierError.call(this,
|
||||
'Field "' + field + '" is defined with at least two different types in indices matching the pattern',
|
||||
errors.MappingConflict);
|
||||
};
|
||||
inherits(errors.MappingConflict, CourierError);
|
||||
|
||||
/**
|
||||
* a field mapping was using a restricted fields name
|
||||
* @param {String} field - the fields which contains the conflict
|
||||
*/
|
||||
errors.RestrictedMapping = function RestrictedMapping(field, index) {
|
||||
var msg = field + ' is a restricted field name';
|
||||
if (index) msg += ', found it while attempting to fetch mapping for index pattern: ' + index;
|
||||
|
||||
CourierError.call(this,
|
||||
msg,
|
||||
errors.RestrictedMapping);
|
||||
};
|
||||
inherits(errors.RestrictedMapping, CourierError);
|
||||
|
||||
/**
|
||||
* a non-critical cache write to elasticseach failed
|
||||
*/
|
||||
errors.CacheWriteFailure = function CacheWriteFailure() {
|
||||
CourierError.call(this,
|
||||
'A Elasticsearch cache write has failed.',
|
||||
errors.CacheWriteFailure);
|
||||
};
|
||||
inherits(errors.CacheWriteFailure, CourierError);
|
||||
|
||||
/**
|
||||
* when a field mapping is requested for an unknown field
|
||||
* @param {String} name - the field name
|
||||
*/
|
||||
errors.FieldNotFoundInCache = function FieldNotFoundInCache(name) {
|
||||
CourierError.call(this,
|
||||
'The ' + name + ' field was not found in the cached mappings',
|
||||
errors.FieldNotFoundInCache);
|
||||
};
|
||||
inherits(errors.FieldNotFoundInCache, CourierError);
|
||||
|
||||
/**
|
||||
* A saved object was not found
|
||||
* @param {String} field - the fields which contains the conflict
|
||||
*/
|
||||
errors.SavedObjectNotFound = function SavedObjectNotFound(type) {
|
||||
this.savedObjectType = type;
|
||||
CourierError.call(this,
|
||||
'Could not locate that ' + type,
|
||||
errors.SavedObjectNotFound);
|
||||
};
|
||||
inherits(errors.SavedObjectNotFound, CourierError);
|
||||
|
||||
/**
|
||||
* Tried to call a method that relies on SearchSource having an indexPattern assigned
|
||||
*/
|
||||
errors.IndexPatternMissingIndices = function IndexPatternMissingIndices(type) {
|
||||
CourierError.call(this,
|
||||
'IndexPattern\'s configured pattern does not match any indices',
|
||||
errors.IndexPatternMissingIndices);
|
||||
};
|
||||
inherits(errors.IndexPatternMissingIndices, CourierError);
|
||||
|
||||
/**
|
||||
* Tried to call a method that relies on SearchSource having an indexPattern assigned
|
||||
*/
|
||||
errors.NoDefinedIndexPatterns = function NoDefinedIndexPatterns(type) {
|
||||
CourierError.call(this,
|
||||
'Define at least one index pattern to continue',
|
||||
errors.NoDefinedIndexPatterns);
|
||||
};
|
||||
inherits(errors.NoDefinedIndexPatterns, CourierError);
|
||||
|
||||
};
|
||||
});
|
33
src/kibana/components/courier/_get_root_search.js
Normal file
33
src/kibana/components/courier/_get_root_search.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
define(function (require) {
|
||||
return function RootSearchFactory(Private, config, $rootScope, timefilter, indexPatterns, Promise) {
|
||||
var SearchSource = Private(require('./data_source/search_source'));
|
||||
|
||||
var source; // the actual search source
|
||||
var prom; // promise that must be resolved before the source is acurrate (updated by loadDefaultPattern)
|
||||
|
||||
var loadDefaultPattern = function () {
|
||||
var defId = config.get('defaultIndex');
|
||||
|
||||
return prom = Promise.cast(defId && indexPatterns.get(defId)).then(function (pattern) {
|
||||
source.set('index', pattern);
|
||||
return source;
|
||||
});
|
||||
};
|
||||
|
||||
var init = function () {
|
||||
source = new SearchSource();
|
||||
source.filter(function (source) {
|
||||
// dynamic time filter will be called in the _flatten phase of things
|
||||
return timefilter.get(source.get('index'));
|
||||
});
|
||||
|
||||
$rootScope.$on('change:config.defaultIndex', loadDefaultPattern);
|
||||
$rootScope.$on('init:config', loadDefaultPattern);
|
||||
return loadDefaultPattern();
|
||||
};
|
||||
|
||||
return function () {
|
||||
return prom || init();
|
||||
};
|
||||
};
|
||||
});
|
|
@ -1,6 +1,8 @@
|
|||
define(function (require) {
|
||||
return function RedirectWhenMissingFn($location, $route, globalState, Notifier, Private) {
|
||||
var SavedObjectNotFound = Private(require('./_errors')).SavedObjectNotFound;
|
||||
var errors = require('errors');
|
||||
|
||||
return function RedirectWhenMissingFn($location, $route, globalState, Notifier) {
|
||||
var SavedObjectNotFound = errors.SavedObjectNotFound;
|
||||
|
||||
var notify = new Notifier();
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
define(function (require) {
|
||||
var errors = require('errors');
|
||||
|
||||
require('services/es');
|
||||
require('services/promises');
|
||||
require('index_patterns/index_patterns');
|
||||
|
||||
require('modules').get('kibana/courier')
|
||||
.service('courier', function ($rootScope, Private, Promise) {
|
||||
.service('courier', function ($rootScope, Private, Promise, indexPatterns) {
|
||||
function Courier() {
|
||||
var courier = this;
|
||||
|
||||
|
@ -15,13 +18,13 @@ define(function (require) {
|
|||
var docLooper = Private(require('./looper/doc'));
|
||||
|
||||
// expose some internal modules
|
||||
courier.errors = Private(require('./_errors'));
|
||||
courier.getRootSearch = Private(require('./_get_root_search'));
|
||||
courier.SavedObject = Private(require('./saved_object/saved_object'));
|
||||
courier.indexPatterns = Private(require('./index_patterns/index_patterns'));
|
||||
courier.indexPatterns = indexPatterns;
|
||||
courier.redirectWhenMissing = Private(require('./_redirect_when_missing'));
|
||||
|
||||
var HastyRefresh = courier.errors.HastyRefresh;
|
||||
var Abort = courier.errors.Abort;
|
||||
var HastyRefresh = errors.HastyRefresh;
|
||||
var Abort = errors.Abort;
|
||||
|
||||
/**
|
||||
* update the time between automatic search requests
|
||||
|
|
|
@ -9,6 +9,8 @@ define(function (require) {
|
|||
var fetch = Private(require('../fetch/fetch'));
|
||||
|
||||
function SourceAbstract(initialState) {
|
||||
this._instanceid = _.uniqueId('data_source');
|
||||
|
||||
this._state = (function () {
|
||||
// state can be serialized as JSON, and passed back in to restore
|
||||
if (initialState) {
|
||||
|
@ -62,9 +64,10 @@ define(function (require) {
|
|||
*/
|
||||
SourceAbstract.prototype.set = function (state, val) {
|
||||
if (typeof state === 'string') {
|
||||
return this[state](val);
|
||||
this[state](val);
|
||||
} else {
|
||||
this._state = state;
|
||||
}
|
||||
this._state = state;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var errors = require('errors');
|
||||
|
||||
return function (Promise, Private, es) {
|
||||
var errors = Private(require('../_errors'));
|
||||
var pendingRequests = Private(require('../_pending_requests'));
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var errors = require('errors');
|
||||
|
||||
var inherits = require('utils/inherits');
|
||||
|
||||
return function DocSourceFactory(Private, Promise, es) {
|
||||
var errors = Private(require('../_errors'));
|
||||
var sendToEs = Private(require('./_doc_send_to_es'));
|
||||
var SourceAbstract = Private(require('./_abstract'));
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
define(function (require) {
|
||||
var inherits = require('utils/inherits');
|
||||
var _ = require('lodash');
|
||||
var errors = require('errors');
|
||||
|
||||
return function SearchSourceFactory(Promise, Private) {
|
||||
var errors = Private(require('../_errors'));
|
||||
var SourceAbstract = Private(require('./_abstract'));
|
||||
|
||||
var FetchFailure = errors.FetchFailure;
|
||||
|
@ -25,8 +25,6 @@ define(function (require) {
|
|||
* @type {Array}
|
||||
*/
|
||||
SearchSource.prototype._methods = [
|
||||
'index',
|
||||
'indexInterval', // monthly, daily, etc
|
||||
'type',
|
||||
'query',
|
||||
'filter',
|
||||
|
@ -38,6 +36,16 @@ define(function (require) {
|
|||
'source'
|
||||
];
|
||||
|
||||
SearchSource.prototype.index = function (indexPattern) {
|
||||
if (indexPattern === void 0) return this._state.index;
|
||||
if (!indexPattern || typeof indexPattern.toIndexList !== 'function') {
|
||||
throw new TypeError('expected indexPattern to be an IndexPattern duck.');
|
||||
}
|
||||
|
||||
this._state.index = indexPattern;
|
||||
return this;
|
||||
};
|
||||
|
||||
SearchSource.prototype.extend = function () {
|
||||
return (new SearchSource()).inherits(this);
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ define(function (require) {
|
|||
|
||||
var handlerCount = 0;
|
||||
errorHandlers.splice(0).forEach(function (handler) {
|
||||
if (handler.source !== req.source) return pendingRequests.push(handler);
|
||||
if (handler.source !== req.source) return errorHandlers.push(handler);
|
||||
handler.defer.resolve(error);
|
||||
handlerCount++;
|
||||
});
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
define(function (require) {
|
||||
return function fetchService(Private, es, Promise, Notifier, $q) {
|
||||
return function fetchService(Private, es, Promise, Notifier) {
|
||||
var _ = require('lodash');
|
||||
var errors = require('errors');
|
||||
|
||||
var docStrategy = Private(require('./strategy/doc'));
|
||||
var searchStrategy = Private(require('./strategy/search'));
|
||||
|
||||
var errors = Private(require('../_errors'));
|
||||
var RequestErrorHandler = Private(require('./_request_error_handler'));
|
||||
var pendingRequests = Private(require('../_pending_requests'));
|
||||
|
||||
|
@ -16,7 +16,27 @@ define(function (require) {
|
|||
var fetchThese = function (strategy, requests, reqErrHandler) {
|
||||
var all, body;
|
||||
|
||||
all = requests.splice(0);
|
||||
// dedupe requests
|
||||
var uniqs = {};
|
||||
all = requests.splice(0).filter(function (req) {
|
||||
var iid = req.source._instanceid;
|
||||
if (!uniqs[iid]) {
|
||||
// this request is unique so far
|
||||
uniqs[iid] = req;
|
||||
// keep the request
|
||||
return true;
|
||||
}
|
||||
|
||||
// the source was requested at least twice
|
||||
var uniq = uniqs[iid];
|
||||
if (uniq._merged) {
|
||||
// already setup the multi-responder
|
||||
uniq._merged.push(req);
|
||||
} else {
|
||||
// put all requests into this array and itterate them all
|
||||
uniq._merged = [uniq, req];
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.map(all, function (req) {
|
||||
return req.source._flatten();
|
||||
|
@ -31,18 +51,32 @@ define(function (require) {
|
|||
body: body
|
||||
})
|
||||
.then(function (resp) {
|
||||
strategy.getResponses(resp).forEach(function (resp) {
|
||||
var req = all.shift();
|
||||
var sendResponse = function (req, resp) {
|
||||
if (resp.error) return reqErrHandler.handle(req, new errors.FetchFailure(resp));
|
||||
else strategy.resolveRequest(req, resp);
|
||||
};
|
||||
|
||||
strategy.getResponses(resp).forEach(function (resp) {
|
||||
var req = all.shift();
|
||||
if (!req._merged) sendResponse(req, resp);
|
||||
else {
|
||||
req._merged.forEach(function (mergedReq) {
|
||||
sendResponse(mergedReq, _.cloneDeep(resp));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// pass the response along to the next promise
|
||||
return resp;
|
||||
})
|
||||
.catch(function (err) {
|
||||
all.forEach(function (req) {
|
||||
var sendFailure = function (req) {
|
||||
reqErrHandler.handle(req, err);
|
||||
};
|
||||
|
||||
all.forEach(function (req) {
|
||||
if (!req._merged) sendFailure(req);
|
||||
else req._merged.forEach(sendFailure);
|
||||
});
|
||||
throw err;
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
define(function (require) {
|
||||
return function FetchStrategyForSearch(Private, Promise, Notifier) {
|
||||
return function FetchStrategyForSearch(Private, Promise, Notifier, timefilter) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var notify = new Notifier();
|
||||
|
@ -14,9 +14,13 @@ define(function (require) {
|
|||
*/
|
||||
requestStatesToBody: function (states) {
|
||||
return states.map(function (state) {
|
||||
var timeBounds = timefilter.getBounds();
|
||||
var indexList = state.index.toIndexList(timeBounds.min, timeBounds.max);
|
||||
|
||||
return JSON.stringify({
|
||||
index: state.index,
|
||||
type: state.type
|
||||
index: indexList,
|
||||
type: state.type,
|
||||
ignore_unavailable: true
|
||||
})
|
||||
+ '\n'
|
||||
+ JSON.stringify(state.body);
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
define(function (require) {
|
||||
return function IndexPatternFactory(Private) {
|
||||
var inherits = require('utils/inherits');
|
||||
|
||||
var mapper = Private(require('./_mapper'));
|
||||
var SavedObject = Private(require('../saved_object/saved_object'));
|
||||
var fieldFormats = Private(require('./_field_formats'));
|
||||
var patternCache = Private(require('./_pattern_cache'));
|
||||
|
||||
function IndexPattern(id) {
|
||||
var pattern = this;
|
||||
SavedObject.call(pattern, {
|
||||
type: 'index-pattern',
|
||||
|
||||
id: id,
|
||||
|
||||
mapping: {
|
||||
title: 'string',
|
||||
timeFieldName: 'string',
|
||||
customFormats: 'json',
|
||||
fields: 'json'
|
||||
},
|
||||
|
||||
defaults: {
|
||||
title: id,
|
||||
customFormats: {}
|
||||
},
|
||||
|
||||
afterESResp: function () {
|
||||
if (pattern.id) {
|
||||
if (!pattern.fields) return pattern.fetchFields();
|
||||
afterFieldsSet();
|
||||
}
|
||||
},
|
||||
|
||||
searchSource: false
|
||||
});
|
||||
|
||||
function afterFieldsSet() {
|
||||
pattern.fieldsByName = {};
|
||||
pattern.fields.forEach(function (field) {
|
||||
pattern.fieldsByName[field.name] = field;
|
||||
|
||||
// non-enumerable type so that it does not get included in the JSON
|
||||
Object.defineProperty(field, 'format', {
|
||||
enumerable: false,
|
||||
get: function () {
|
||||
var formatName = pattern.customFormats && pattern.customFormats[field.name];
|
||||
return formatName ? fieldFormats.byName[formatName] : fieldFormats.defaultByType[field.type];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pattern.refreshFields = function () {
|
||||
return mapper.clearCache(pattern.id)
|
||||
.then(function () {
|
||||
return pattern.fetchFields();
|
||||
});
|
||||
};
|
||||
|
||||
pattern.fetchFields = function () {
|
||||
return mapper.getFieldsForIndexPattern(pattern.id, true)
|
||||
.then(function (fields) {
|
||||
pattern.fields = fields;
|
||||
afterFieldsSet();
|
||||
return pattern.save();
|
||||
});
|
||||
};
|
||||
|
||||
pattern.toJSON = function () {
|
||||
return pattern.id;
|
||||
};
|
||||
|
||||
pattern.toString = function () {
|
||||
return '' + this.toJSON();
|
||||
};
|
||||
}
|
||||
inherits(IndexPattern, SavedObject);
|
||||
|
||||
return IndexPattern;
|
||||
};
|
||||
});
|
|
@ -1,8 +1,8 @@
|
|||
define(function (require) {
|
||||
return function SearchLooperService(Private, Promise) {
|
||||
var errors = require('errors');
|
||||
var fetch = Private(require('../fetch/fetch'));
|
||||
var Looper = Private(require('./_looper'));
|
||||
var errors = Private(require('../_errors'));
|
||||
|
||||
// track the currently executing search resquest
|
||||
var _activeAutoSearch = null;
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
define(function (require) {
|
||||
return function RootSearchFactory(Private, config, $rootScope, timefilter) {
|
||||
return function makeRootSearch() {
|
||||
var SearchSource = Private(require('../data_source/search_source'));
|
||||
var indexPatterns = Private(require('../index_patterns/index_patterns'));
|
||||
|
||||
var source = (new SearchSource())
|
||||
.index(config.get('defaultIndex'))
|
||||
.filter(function (source) {
|
||||
return indexPatterns.get(source.get('index'))
|
||||
.then(function (indexPattern) {
|
||||
// dynamic time filter will be called in the _flatten phase of things
|
||||
return timefilter.get(indexPattern);
|
||||
});
|
||||
});
|
||||
|
||||
$rootScope.$on('change:config.defaultIndex', function () {
|
||||
source.index(config.get('defaultIndex'));
|
||||
});
|
||||
|
||||
return source;
|
||||
};
|
||||
};
|
||||
});
|
|
@ -1,22 +1,13 @@
|
|||
define(function (require) {
|
||||
return function SavedObjectFactory(configFile, Promise, Private, Notifier) {
|
||||
var errors = require('errors');
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
|
||||
var DocSource = Private(require('../data_source/doc_source'));
|
||||
var SearchSource = Private(require('../data_source/search_source'));
|
||||
|
||||
var errors = Private(require('../_errors'));
|
||||
var mappingSetup = Private(require('./_mapping_setup'));
|
||||
|
||||
var json = {
|
||||
_serialize: function (val) {
|
||||
if (val != null) return angular.toJson(val);
|
||||
},
|
||||
_deserialize: function (val) {
|
||||
if (val != null) return JSON.parse(val);
|
||||
}
|
||||
};
|
||||
var mappingSetup = Private(require('utils/mapping_setup'));
|
||||
var getRootSearch = Private(require('../_get_root_search'));
|
||||
|
||||
function SavedObject(config) {
|
||||
if (!_.isObject(config)) config = {};
|
||||
|
@ -39,19 +30,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
// mapping definition for the fields that this object will expose
|
||||
var mapping = _.mapValues(config.mapping || {}, function (val, prop) {
|
||||
// allow shortcuts for the field types, by just setting the value
|
||||
// to the type name
|
||||
if (typeof val === 'string') val = { type: val };
|
||||
|
||||
if (val.type === 'json') {
|
||||
val.type = 'string';
|
||||
val._serialize = json._serialize;
|
||||
val._deserialize = json._deserialize;
|
||||
}
|
||||
|
||||
return val;
|
||||
});
|
||||
var mapping = mappingSetup.expandShorthand(config.mapping);
|
||||
|
||||
// default field values, assigned when the source is loaded
|
||||
var defaults = config.defaults || {};
|
||||
|
@ -83,12 +62,13 @@ define(function (require) {
|
|||
.id(obj.id);
|
||||
|
||||
// by default, the search source should inherit from the rootSearch
|
||||
if (obj.searchSource) {
|
||||
obj.searchSource.inherits(SavedObject.rootSearch());
|
||||
}
|
||||
return Promise.cast(obj.searchSource && getRootSearch())
|
||||
.then(function (rootSearch) {
|
||||
if (rootSearch) obj.searchSource.inherits(rootSearch);
|
||||
|
||||
// check that the mapping for this type is defined
|
||||
return mappingSetup.isDefined(type)
|
||||
// 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;
|
||||
|
@ -208,12 +188,6 @@ define(function (require) {
|
|||
|
||||
}
|
||||
|
||||
SavedObject.rootSearch = function () {
|
||||
var rootSearch = Private(require('./_root_search'))();
|
||||
SavedObject.rootSearch = function () { return rootSearch; };
|
||||
return rootSearch;
|
||||
};
|
||||
|
||||
return SavedObject;
|
||||
};
|
||||
});
|
160
src/kibana/components/errors.js
Normal file
160
src/kibana/components/errors.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var inherits = require('utils/inherits');
|
||||
|
||||
var canStack = (function () {
|
||||
var err = new Error();
|
||||
return !!err.stack;
|
||||
}());
|
||||
|
||||
var errors = {};
|
||||
|
||||
// abstract error class
|
||||
function KbnError(msg, constructor) {
|
||||
this.message = msg;
|
||||
|
||||
Error.call(this, this.message);
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, constructor || KbnError);
|
||||
} else if (canStack) {
|
||||
this.stack = (new Error()).stack;
|
||||
} else {
|
||||
this.stack = '';
|
||||
}
|
||||
}
|
||||
errors.KbnError = KbnError;
|
||||
inherits(KbnError, Error);
|
||||
|
||||
/**
|
||||
* HastyRefresh error class
|
||||
* @param {String} [msg] - An error message that will probably end up in a log.
|
||||
*/
|
||||
errors.HastyRefresh = function HastyRefresh() {
|
||||
KbnError.call(this,
|
||||
'Courier attempted to start a query before the previous had finished.',
|
||||
errors.HastyRefresh);
|
||||
};
|
||||
inherits(errors.HastyRefresh, KbnError);
|
||||
|
||||
/**
|
||||
* Request Failure - When an entire mutli request fails
|
||||
* @param {Error} err - the Error that came back
|
||||
* @param {Object} resp - optional HTTP response
|
||||
*/
|
||||
errors.RequestFailure = function RequestFailure(err, resp) {
|
||||
KbnError.call(this,
|
||||
'Request to Elasticsearch failed: ' + JSON.stringify(resp || err.message),
|
||||
errors.RequestFailure);
|
||||
|
||||
this.origError = err;
|
||||
this.resp = resp;
|
||||
};
|
||||
inherits(errors.RequestFailure, KbnError);
|
||||
|
||||
/**
|
||||
* FetchFailure Error - when there is an error getting a doc or search within
|
||||
* a multi-response response body
|
||||
* @param {String} [msg] - An error message that will probably end up in a log.
|
||||
*/
|
||||
errors.FetchFailure = function FetchFailure(resp) {
|
||||
KbnError.call(this,
|
||||
'Failed to get the doc: ' + JSON.stringify(resp),
|
||||
errors.FetchFailure);
|
||||
|
||||
this.resp = resp;
|
||||
};
|
||||
inherits(errors.FetchFailure, KbnError);
|
||||
|
||||
|
||||
/**
|
||||
* A doc was re-indexed but it was out of date.
|
||||
* @param {Object} resp - The response from es (one of the multi-response responses).
|
||||
*/
|
||||
errors.VersionConflict = function VersionConflict(resp) {
|
||||
KbnError.call(this,
|
||||
'Failed to store document changes do to a version conflict.',
|
||||
errors.VersionConflict);
|
||||
|
||||
this.resp = resp;
|
||||
};
|
||||
inherits(errors.VersionConflict, KbnError);
|
||||
|
||||
|
||||
/**
|
||||
* there was a conflict storing a doc
|
||||
* @param {String} field - the fields which contains the conflict
|
||||
*/
|
||||
errors.MappingConflict = function MappingConflict(field) {
|
||||
KbnError.call(this,
|
||||
'Field "' + field + '" is defined with at least two different types in indices matching the pattern',
|
||||
errors.MappingConflict);
|
||||
};
|
||||
inherits(errors.MappingConflict, KbnError);
|
||||
|
||||
/**
|
||||
* a field mapping was using a restricted fields name
|
||||
* @param {String} field - the fields which contains the conflict
|
||||
*/
|
||||
errors.RestrictedMapping = function RestrictedMapping(field, index) {
|
||||
var msg = field + ' is a restricted field name';
|
||||
if (index) msg += ', found it while attempting to fetch mapping for index pattern: ' + index;
|
||||
|
||||
KbnError.call(this, msg, errors.RestrictedMapping);
|
||||
};
|
||||
inherits(errors.RestrictedMapping, KbnError);
|
||||
|
||||
/**
|
||||
* a non-critical cache write to elasticseach failed
|
||||
*/
|
||||
errors.CacheWriteFailure = function CacheWriteFailure() {
|
||||
KbnError.call(this,
|
||||
'A Elasticsearch cache write has failed.',
|
||||
errors.CacheWriteFailure);
|
||||
};
|
||||
inherits(errors.CacheWriteFailure, KbnError);
|
||||
|
||||
/**
|
||||
* when a field mapping is requested for an unknown field
|
||||
* @param {String} name - the field name
|
||||
*/
|
||||
errors.FieldNotFoundInCache = function FieldNotFoundInCache(name) {
|
||||
KbnError.call(this,
|
||||
'The ' + name + ' field was not found in the cached mappings',
|
||||
errors.FieldNotFoundInCache);
|
||||
};
|
||||
inherits(errors.FieldNotFoundInCache, KbnError);
|
||||
|
||||
/**
|
||||
* A saved object was not found
|
||||
* @param {String} field - the fields which contains the conflict
|
||||
*/
|
||||
errors.SavedObjectNotFound = function SavedObjectNotFound(type) {
|
||||
this.savedObjectType = type;
|
||||
KbnError.call(this,
|
||||
'Could not locate that ' + type,
|
||||
errors.SavedObjectNotFound);
|
||||
};
|
||||
inherits(errors.SavedObjectNotFound, KbnError);
|
||||
|
||||
/**
|
||||
* Tried to call a method that relies on SearchSource having an indexPattern assigned
|
||||
*/
|
||||
errors.IndexPatternMissingIndices = function IndexPatternMissingIndices(type) {
|
||||
KbnError.call(this,
|
||||
'IndexPattern\'s configured pattern does not match any indices',
|
||||
errors.IndexPatternMissingIndices);
|
||||
};
|
||||
inherits(errors.IndexPatternMissingIndices, KbnError);
|
||||
|
||||
/**
|
||||
* Tried to call a method that relies on SearchSource having an indexPattern assigned
|
||||
*/
|
||||
errors.NoDefinedIndexPatterns = function NoDefinedIndexPatterns(type) {
|
||||
KbnError.call(this,
|
||||
'Define at least one index pattern to continue',
|
||||
errors.NoDefinedIndexPatterns);
|
||||
};
|
||||
inherits(errors.NoDefinedIndexPatterns, KbnError);
|
||||
|
||||
return errors;
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
define(function (require) {
|
||||
return function EnsureSomeIndexPatternsFn(Private, Notifier, $location, $route) {
|
||||
var errors = Private(require('../_errors'));
|
||||
var errors = require('errors');
|
||||
var notify = new Notifier();
|
||||
|
||||
return function ensureSomeIndexPatterns() {
|
157
src/kibana/components/index_patterns/_index_pattern.js
Normal file
157
src/kibana/components/index_patterns/_index_pattern.js
Normal file
|
@ -0,0 +1,157 @@
|
|||
define(function (require) {
|
||||
return function IndexPatternFactory(Private, timefilter, configFile, Notifier) {
|
||||
var _ = require('lodash');
|
||||
var angular = require('angular');
|
||||
var errors = require('errors');
|
||||
|
||||
var mapper = Private(require('./_mapper'));
|
||||
var fieldFormats = Private(require('./_field_formats'));
|
||||
var patternCache = Private(require('./_pattern_cache'));
|
||||
var intervals = Private(require('./_intervals'));
|
||||
var mappingSetup = Private(require('utils/mapping_setup'));
|
||||
var DocSource = Private(require('courier/data_source/doc_source'));
|
||||
|
||||
var type = 'index-pattern';
|
||||
|
||||
var notify = new Notifier();
|
||||
|
||||
var mapping = mappingSetup.expandShorthand({
|
||||
title: 'string',
|
||||
timeFieldName: 'string',
|
||||
intervalName: 'string',
|
||||
customFormats: 'json',
|
||||
fields: 'json'
|
||||
});
|
||||
|
||||
function IndexPattern(id) {
|
||||
var pattern = this;
|
||||
|
||||
// set defaults
|
||||
pattern.id = id;
|
||||
pattern.title = id;
|
||||
pattern.customFormats = {};
|
||||
|
||||
var docSource = new DocSource();
|
||||
|
||||
pattern.init = function () {
|
||||
// tell the docSource where to find the doc
|
||||
docSource
|
||||
.index(configFile.kibanaIndex)
|
||||
.type(type)
|
||||
.id(pattern.id);
|
||||
|
||||
// check that the mapping for this type is defined
|
||||
return mappingSetup.isDefined(type)
|
||||
.then(function (defined) {
|
||||
if (defined) return true;
|
||||
return mappingSetup.setup(type, mapping);
|
||||
})
|
||||
.then(function () {
|
||||
// If there is no id, then there is no document to fetch from elasticsearch
|
||||
if (!pattern.id) return;
|
||||
|
||||
// fetch the object from ES
|
||||
return docSource.fetch()
|
||||
.then(function applyESResp(resp) {
|
||||
if (!resp.found) throw new errors.SavedObjectNotFound(type);
|
||||
|
||||
// deserialize any json fields
|
||||
_.forOwn(mapping, function ittr(fieldMapping, name) {
|
||||
if (fieldMapping._deserialize) {
|
||||
resp._source[name] = fieldMapping._deserialize(resp._source[name], resp, name, fieldMapping);
|
||||
}
|
||||
});
|
||||
|
||||
// Give obj all of the values in _source.fields
|
||||
_.assign(pattern, resp._source);
|
||||
|
||||
if (pattern.id) {
|
||||
if (!pattern.fields) return pattern.fetchFields();
|
||||
afterFieldsSet();
|
||||
}
|
||||
|
||||
// Any time obj is updated, re-call applyESResp
|
||||
docSource.onUpdate().then(applyESResp, notify.fatal);
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
// return our obj as the result of init()
|
||||
return pattern;
|
||||
});
|
||||
};
|
||||
|
||||
function afterFieldsSet() {
|
||||
pattern.fieldsByName = {};
|
||||
pattern.fields.forEach(function (field) {
|
||||
pattern.fieldsByName[field.name] = field;
|
||||
|
||||
// non-enumerable type so that it does not get included in the JSON
|
||||
Object.defineProperty(field, 'format', {
|
||||
enumerable: false,
|
||||
get: function () {
|
||||
var formatName = pattern.customFormats && pattern.customFormats[field.name];
|
||||
return formatName ? fieldFormats.byName[formatName] : fieldFormats.defaultByType[field.type];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pattern.toIndexList = function (start, stop) {
|
||||
var interval = this.intervalName && _.find(intervals, { name: this.intervalName });
|
||||
if (interval) {
|
||||
return intervals.toIndexList(pattern.id, interval, start, stop);
|
||||
} else {
|
||||
return pattern.id;
|
||||
}
|
||||
};
|
||||
|
||||
pattern.save = function () {
|
||||
var body = {};
|
||||
|
||||
// serialize json fields
|
||||
_.forOwn(mapping, function (fieldMapping, fieldName) {
|
||||
if (pattern[fieldName] != null) {
|
||||
body[fieldName] = (fieldMapping._serialize)
|
||||
? fieldMapping._serialize(pattern[fieldName])
|
||||
: pattern[fieldName];
|
||||
}
|
||||
});
|
||||
|
||||
// ensure that the docSource has the current pattern.id
|
||||
docSource.id(pattern.id);
|
||||
|
||||
// index the document
|
||||
return docSource.doIndex(body)
|
||||
.then(function (id) {
|
||||
pattern.id = id;
|
||||
return pattern.id;
|
||||
});
|
||||
};
|
||||
|
||||
pattern.refreshFields = function () {
|
||||
return mapper.clearCache(pattern)
|
||||
.then(function () {
|
||||
return pattern.fetchFields();
|
||||
});
|
||||
};
|
||||
|
||||
pattern.fetchFields = function () {
|
||||
return mapper.getFieldsForIndexPattern(pattern, true)
|
||||
.then(function (fields) {
|
||||
pattern.fields = fields;
|
||||
afterFieldsSet();
|
||||
return pattern.save();
|
||||
});
|
||||
};
|
||||
|
||||
pattern.toJSON = function () {
|
||||
return pattern.id;
|
||||
};
|
||||
|
||||
pattern.toString = function () {
|
||||
return '' + pattern.toJSON();
|
||||
};
|
||||
}
|
||||
return IndexPattern;
|
||||
};
|
||||
});
|
71
src/kibana/components/index_patterns/_intervals.js
Normal file
71
src/kibana/components/index_patterns/_intervals.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
define(function (require) {
|
||||
return function IndexNameIntervalsService(timefilter) {
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
|
||||
var intervals = [
|
||||
{
|
||||
name: 'hours',
|
||||
display: 'Hourly'
|
||||
},
|
||||
{
|
||||
name: 'days',
|
||||
display: 'Daily'
|
||||
},
|
||||
{
|
||||
name: 'weeks',
|
||||
display: 'Weekly'
|
||||
},
|
||||
{
|
||||
name: 'months',
|
||||
display: 'Monthly'
|
||||
},
|
||||
{
|
||||
name: 'years',
|
||||
display: 'Yearly'
|
||||
}
|
||||
];
|
||||
|
||||
intervals.toIndexList = function (format, interval, a, b) {
|
||||
var bounds;
|
||||
|
||||
// setup the range that the list will span, return two moment objects that
|
||||
// are in proper order. a and b can be numbers to specify to go before or after now (respectively)
|
||||
// a certain number of times, based on the interval
|
||||
var range = [ [a, 'min'], [b, 'max'] ].map(function (v) {
|
||||
var val = v[0];
|
||||
var bound = v[1];
|
||||
|
||||
// grab a bound from the time filter
|
||||
if (val == null) {
|
||||
bounds = bounds || timefilter.getBounds();
|
||||
return bounds[bound];
|
||||
}
|
||||
|
||||
if (_.isNumeric(val)) return moment().add(interval.name, val);
|
||||
if (moment.isMoment(val)) return val;
|
||||
return moment(val);
|
||||
}).sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
if (typeof interval === 'string') {
|
||||
interval = _.find(intervals, { name: interval });
|
||||
if (!interval) throw new Error('Interval must be one of ' + _.pluck(intervals, 'name'));
|
||||
}
|
||||
|
||||
var indexList = [];
|
||||
var start = range.shift();
|
||||
// turn stop into milliseconds to that it's not constantly converted by the while condition
|
||||
var stop = range.shift().valueOf();
|
||||
|
||||
while (start <= stop) {
|
||||
start.add(interval.name, 1);
|
||||
indexList.push(start.format(format));
|
||||
}
|
||||
return indexList;
|
||||
};
|
||||
|
||||
return intervals;
|
||||
};
|
||||
});
|
|
@ -1,9 +1,10 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
return function MapperService(Private, Promise, es, configFile) {
|
||||
var IndexPatternMissingIndices = Private(require('../_errors')).IndexPatternMissingIndices;
|
||||
var _ = require('lodash');
|
||||
|
||||
var IndexPatternMissingIndices = require('errors').IndexPatternMissingIndices;
|
||||
var transformMappingIntoFields = Private(require('./_transform_mapping_into_fields'));
|
||||
var intervals = Private(require('./_intervals'));
|
||||
|
||||
var LocalCache = Private(require('./_local_cache'));
|
||||
|
||||
|
@ -23,19 +24,22 @@ define(function (require) {
|
|||
* @async
|
||||
*/
|
||||
mapper.getFieldsForIndexPattern = function (indexPattern, skipIndexPatternCache) {
|
||||
var cache;
|
||||
if (cache = fieldCache.get(indexPattern)) return Promise.resolve(cache);
|
||||
var id = indexPattern.id;
|
||||
var indexList = indexPattern.toIndexList(0, 5);
|
||||
|
||||
var cache = fieldCache.get(id);
|
||||
if (cache) return Promise.resolve(cache);
|
||||
|
||||
if (!skipIndexPatternCache) {
|
||||
return es.get({
|
||||
index: configFile.kibanaIndex,
|
||||
type: 'index-pattern',
|
||||
id: indexPattern,
|
||||
id: id,
|
||||
_sourceInclude: ['fields']
|
||||
})
|
||||
.then(function (resp) {
|
||||
if (resp.found && resp._source.fields) {
|
||||
fieldCache.set(indexPattern, JSON.parse(resp._source.fields));
|
||||
fieldCache.set(id, JSON.parse(resp._source.fields));
|
||||
}
|
||||
return mapper.getFieldsForIndexPattern(indexPattern, true);
|
||||
});
|
||||
|
@ -43,8 +47,10 @@ define(function (require) {
|
|||
|
||||
return es.indices.getFieldMapping({
|
||||
// TODO: Change index to be the resolved in some way, last three months, last hour, last year, whatever
|
||||
index: indexPattern,
|
||||
index: indexList,
|
||||
field: '*',
|
||||
ignoreUnavailable: _.isArray(indexList),
|
||||
allowNoIndices: false
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (err.status >= 400) {
|
||||
|
@ -57,8 +63,8 @@ define(function (require) {
|
|||
})
|
||||
.then(transformMappingIntoFields)
|
||||
.then(function (fields) {
|
||||
fieldCache.set(indexPattern, fields);
|
||||
return fieldCache.get(indexPattern);
|
||||
fieldCache.set(id, fields);
|
||||
return fieldCache.get(id);
|
||||
});
|
||||
};
|
||||
|
31
src/kibana/components/index_patterns/_pattern_to_wildcard.js
Normal file
31
src/kibana/components/index_patterns/_pattern_to_wildcard.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
define(function (require) {
|
||||
return function PatternToWildcardFn() {
|
||||
return function (format) {
|
||||
var wildcard = '';
|
||||
var inEscape = false;
|
||||
var inPattern = false;
|
||||
|
||||
for (var i = 0; i < format.length; i++) {
|
||||
var ch = format.charAt(i);
|
||||
switch (ch) {
|
||||
case '[':
|
||||
if (!inEscape) inEscape = true;
|
||||
else wildcard += ch;
|
||||
break;
|
||||
case ']':
|
||||
if (inEscape) inEscape = false;
|
||||
else wildcard += ch;
|
||||
break;
|
||||
default:
|
||||
if (inEscape) wildcard += ch;
|
||||
else if (!inPattern) {
|
||||
wildcard += '*';
|
||||
inPattern = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wildcard;
|
||||
};
|
||||
};
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
define(function (require) {
|
||||
return function transformMappingIntoFields(Private, configFile) {
|
||||
var _ = require('lodash');
|
||||
var MappingConflict = Private(require('../_errors')).MappingConflict;
|
||||
var MappingConflict = require('errors').MappingConflict;
|
||||
var castMappingType = Private(require('./_cast_mapping_type'));
|
||||
|
||||
var reservedFields = {
|
|
@ -1,13 +1,13 @@
|
|||
define(function (require) {
|
||||
return function IndexPatternsService(configFile, es, Notifier, Private, Promise) {
|
||||
var module = require('modules').get('kibana/index_patterns');
|
||||
|
||||
module.service('indexPatterns', function (configFile, es, Notifier, Private, Promise) {
|
||||
var indexPatterns = this;
|
||||
var _ = require('lodash');
|
||||
var errors = require('errors');
|
||||
|
||||
var IndexPattern = Private(require('./_index_pattern'));
|
||||
var patternCache = Private(require('./_pattern_cache'));
|
||||
var SourceAbstract = Private(require('../data_source/_abstract'));
|
||||
|
||||
var errors = Private(require('../_errors'));
|
||||
|
||||
var notify = new Notifier({ location: 'IndexPatterns Service'});
|
||||
|
||||
|
@ -39,23 +39,14 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
indexPatterns.getFieldsFor = function (indexish) {
|
||||
// pull the index string out of Source objects
|
||||
if (indexish instanceof SourceAbstract) {
|
||||
indexish = indexish.get('index');
|
||||
}
|
||||
|
||||
return Promise.cast(_.isObject(indexish) ? indexish : indexPatterns.get(indexish))
|
||||
.then(function (indexPattern) {
|
||||
return indexPattern.fields;
|
||||
});
|
||||
};
|
||||
|
||||
indexPatterns.errors = {
|
||||
MissingIndices: errors.IndexPatternMissingIndices
|
||||
};
|
||||
|
||||
indexPatterns.ensureSome = Private(require('./_ensure_some'));
|
||||
indexPatterns.cache = patternCache;
|
||||
};
|
||||
indexPatterns.intervals = Private(require('./_intervals'));
|
||||
indexPatterns.mapper = Private(require('./_mapper'));
|
||||
indexPatterns.patternToWildcard = Private(require('./_pattern_to_wildcard'));
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@ define(function (require) {
|
|||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
info: '=',
|
||||
info: '@',
|
||||
placement: '@'
|
||||
},
|
||||
template: html,
|
||||
|
|
|
@ -2,11 +2,13 @@ require.config({
|
|||
baseUrl: './kibana',
|
||||
paths: {
|
||||
// components
|
||||
kibana: './index',
|
||||
courier: './components/courier',
|
||||
config: './components/config',
|
||||
setup: './components/setup',
|
||||
kibana: './index',
|
||||
config: './components/config',
|
||||
errors: './components/errors',
|
||||
notify: './components/notify',
|
||||
courier: './components/courier',
|
||||
index_patterns: './components/index_patterns',
|
||||
state_management: './components/state_management',
|
||||
|
||||
// special utils
|
||||
|
|
|
@ -7209,3 +7209,24 @@ saved-object-finder .finder-options > li.active a {
|
|||
color: #b4bcc2;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
.form-control[disabled],
|
||||
.form-control[readonly],
|
||||
fieldset[disabled] .form-control {
|
||||
cursor: default;
|
||||
opacity: .8;
|
||||
}
|
||||
input[type="radio"][disabled],
|
||||
input[type="checkbox"][disabled],
|
||||
.radio[disabled],
|
||||
.radio-inline[disabled],
|
||||
.checkbox[disabled],
|
||||
.checkbox-inline[disabled],
|
||||
fieldset[disabled] input[type="radio"],
|
||||
fieldset[disabled] input[type="checkbox"],
|
||||
fieldset[disabled] .radio,
|
||||
fieldset[disabled] .radio-inline,
|
||||
fieldset[disabled] .checkbox,
|
||||
fieldset[disabled] .checkbox-inline {
|
||||
cursor: default;
|
||||
opacity: .8;
|
||||
}
|
||||
|
|
|
@ -216,3 +216,27 @@ saved-object-finder {
|
|||
color: @text-muted;
|
||||
padding: @padding-base-vertical @padding-base-horizontal;
|
||||
}
|
||||
|
||||
|
||||
//== override the disabled cursor that doesn't work everywhere
|
||||
.form-control{
|
||||
&[disabled],
|
||||
&[readonly],
|
||||
fieldset[disabled] & {
|
||||
cursor: default;
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"],
|
||||
.radio,
|
||||
.radio-inline,
|
||||
.checkbox,
|
||||
.checkbox-inline {
|
||||
&[disabled],
|
||||
fieldset[disabled] & {
|
||||
cursor: default;
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
define(function () {
|
||||
return function MappingSetupService(configFile, es) {
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var mappingSetup = this;
|
||||
|
||||
var json = {
|
||||
_serialize: function (val) {
|
||||
if (val != null) return angular.toJson(val);
|
||||
},
|
||||
_deserialize: function (val) {
|
||||
if (val != null) return JSON.parse(val);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to create the mappings, but that should only happen one at a time
|
||||
*/
|
||||
|
@ -26,6 +36,22 @@ define(function () {
|
|||
});
|
||||
});
|
||||
|
||||
mappingSetup.expandShorthand = function (sh) {
|
||||
return _.mapValues(sh || {}, function (val, prop) {
|
||||
// allow shortcuts for the field types, by just setting the value
|
||||
// to the type name
|
||||
if (typeof val === 'string') val = { type: val };
|
||||
|
||||
if (val.type === 'json') {
|
||||
val.type = 'string';
|
||||
val._serialize = json._serialize;
|
||||
val._deserialize = json._deserialize;
|
||||
}
|
||||
|
||||
return val;
|
||||
});
|
||||
};
|
||||
|
||||
mappingSetup.isDefined = function (type) {
|
||||
return getKnownKibanaTypes()
|
||||
.then(function (knownTypes) {
|
|
@ -55,6 +55,9 @@ define(function (require) {
|
|||
return memo.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
},
|
||||
isNumeric: function (v) {
|
||||
return !isNaN(v) && _.isNumber(v);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,182 +0,0 @@
|
|||
/*
|
||||
define(function (require) {
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var _ = require('lodash');
|
||||
require('angular-mocks');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
var courier, es, $httpBackend;
|
||||
|
||||
beforeEach(inject(function ($injector) {
|
||||
courier = $injector.get('courier');
|
||||
es = $injector.get('es');
|
||||
$httpBackend = $injector.get('$httpBackend');
|
||||
}));
|
||||
|
||||
describe('DocSource class', function () {
|
||||
it('tracks the version of the document', function (done) {
|
||||
var version = 51;
|
||||
|
||||
var source = courier
|
||||
.createSource('doc').index('fake').type('fake').id('fake')
|
||||
.on('results', function (doc) {
|
||||
expect(source._getVersion()).to.eql(version);
|
||||
expect(courier._getRefFor(source).version).to.eql(version);
|
||||
done();
|
||||
});
|
||||
|
||||
courier.start();
|
||||
});
|
||||
|
||||
it('updates to a doc will propogate to other docs with the same index/type/id', function (done) {
|
||||
var client = (function () {
|
||||
// fake server state
|
||||
var version = 0;
|
||||
var doc = { hi: 'fallacy' };
|
||||
|
||||
return stubClient(es, {
|
||||
update: function (params, cb) {
|
||||
_.assign(doc, params.body.doc);
|
||||
version++;
|
||||
cb(void 0, { ok: true });
|
||||
},
|
||||
default: function (method, params, cb) {
|
||||
cb(void 0, stubClient.doc({ _source: doc, _version: version }));
|
||||
}
|
||||
});
|
||||
}());
|
||||
|
||||
var update = { hi: 'truth' };
|
||||
|
||||
// updating this
|
||||
var pitcher = courier.createSource('doc').index('fake').type('fake').id('fake')
|
||||
.doUpdate(update);
|
||||
|
||||
// should update this
|
||||
var catcher = courier.createSource('doc').index('fake').type('fake').id('fake')
|
||||
.on('results', function (doc) {
|
||||
expect(doc._source).to.eql(update);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('clears the stored version when a document has been deleted', function (done) {
|
||||
var client = (function () {
|
||||
// fake server state
|
||||
var doc = { hi: 'fallacy' };
|
||||
|
||||
return stubbedClient({
|
||||
delete: function (params, cb) {
|
||||
doc = null;
|
||||
cb(void 0, { ok: true });
|
||||
},
|
||||
default: function (method, params, cb) {
|
||||
if (doc) {
|
||||
cb(void 0, stubbedClient.doc({ _source: doc }));
|
||||
} else {
|
||||
cb(void 0, stubbedClient.doc({ found: false, _source: null, _version: null }));
|
||||
}
|
||||
}
|
||||
});
|
||||
}());
|
||||
var courier = createCourier(client);
|
||||
|
||||
var source = courier.createSource('doc')
|
||||
.index('fake')
|
||||
.type('fake')
|
||||
.id('fake')
|
||||
.on('results', function (doc) {
|
||||
if (doc.found) {
|
||||
client.delete({}, function () {
|
||||
source.fetch();
|
||||
});
|
||||
} else {
|
||||
expect(courier._getRefFor(source).version).to.be(void 0);
|
||||
expect(source._getVersion()).to.be(void 0);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
courier.start();
|
||||
});
|
||||
|
||||
it('checks localStorage for changes to the stored version, which will trigger the doc to be refetched', function (done) {
|
||||
var version = 11234;
|
||||
var courier = createCourier(stubbedClient(function (method, params, cb) {
|
||||
cb(void 0, stubbedClient.doc({ _version: version }));
|
||||
}));
|
||||
|
||||
var count = 0;
|
||||
courier.docInterval(10);
|
||||
|
||||
var source = courier.createSource('doc')
|
||||
.index('fake')
|
||||
.type('fake')
|
||||
.id('fake')
|
||||
.on('results', function (doc) {
|
||||
switch (count++) {
|
||||
case 0:
|
||||
// simulate removing the version in another tab
|
||||
localStorage.removeItem(source._versionKey());
|
||||
// get version should now be returning undefined
|
||||
expect(source._getVersion()).to.eql(void 0);
|
||||
// tell the courier to check docs 1 ms now
|
||||
courier.docInterval(1);
|
||||
break;
|
||||
case 1:
|
||||
// doc version should now be populated
|
||||
expect(source._getVersion()).to.eql(version);
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
courier.start();
|
||||
});
|
||||
|
||||
describe('#doIndex', function () {
|
||||
it('reindexes the doc using the hash passed in', function (done) {
|
||||
var client = (function () {
|
||||
var version = 1;
|
||||
var doc = { initial: true };
|
||||
|
||||
return stubbedClient({
|
||||
index: function (params, cb) {
|
||||
doc = _.clone(params.body);
|
||||
version ++;
|
||||
cb(void 0, { ok: true });
|
||||
},
|
||||
mget: function (params, cb) {
|
||||
cb(void 0, stubbedClient.doc({ _version: version, _source: doc }));
|
||||
}
|
||||
});
|
||||
}());
|
||||
var courier = createCourier(client);
|
||||
|
||||
var count = 0;
|
||||
courier.docInterval(10);
|
||||
|
||||
var source = courier.createSource('doc')
|
||||
.index('fake')
|
||||
.type('fake')
|
||||
.id('fake')
|
||||
.on('results', function (doc) {
|
||||
switch (count ++) {
|
||||
case 0:
|
||||
expect(doc._source).to.have.property('initial', true);
|
||||
source.doIndex({ second: true });
|
||||
break;
|
||||
case 1:
|
||||
expect(doc._source).to.not.have.property('initial');
|
||||
expect(doc._source).to.have.property('second', true);
|
||||
done();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
courier.start();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue