mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Merge pull request #5252 from spalger/fix/segmentedIndexFetchOrder
Fix/segmented index fetch order
This commit is contained in:
commit
1436b7e05b
6 changed files with 187 additions and 60 deletions
|
@ -0,0 +1,62 @@
|
|||
describe('ui/courier/fetch/request/segmented/_createQueue', () => {
|
||||
const sinon = require('auto-release-sinon');
|
||||
const expect = require('expect.js');
|
||||
const ngMock = require('ngMock');
|
||||
|
||||
let Promise;
|
||||
let $rootScope;
|
||||
let SegmentedReq;
|
||||
let MockSource;
|
||||
|
||||
require('testUtils/noDigestPromises').activateForSuite();
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject((Private, $injector) => {
|
||||
Promise = $injector.get('Promise');
|
||||
$rootScope = $injector.get('$rootScope');
|
||||
SegmentedReq = Private(require('ui/courier/fetch/request/segmented'));
|
||||
|
||||
const StubbedSearchSourceProvider = require('fixtures/stubbed_search_source');
|
||||
MockSource = class {
|
||||
constructor() {
|
||||
return $injector.invoke(StubbedSearchSourceProvider);
|
||||
}
|
||||
};
|
||||
}));
|
||||
|
||||
it('manages the req._queueCreated flag', async function () {
|
||||
const req = new SegmentedReq(new MockSource());
|
||||
req._queueCreated = null;
|
||||
|
||||
var promise = req._createQueue();
|
||||
expect(req._queueCreated).to.be(false);
|
||||
await promise;
|
||||
expect(req._queueCreated).to.be(true);
|
||||
});
|
||||
|
||||
it('relies on indexPattern.toIndexList to generate queue', async function () {
|
||||
const source = new MockSource();
|
||||
const ip = source.get('index');
|
||||
const indices = [1,2,3];
|
||||
sinon.stub(ip, 'toIndexList').returns(Promise.resolve(indices));
|
||||
|
||||
const req = new SegmentedReq(source);
|
||||
const output = await req._createQueue();
|
||||
expect(output).to.equal(indices);
|
||||
});
|
||||
|
||||
it('tells the index pattern its direction', async function () {
|
||||
const source = new MockSource();
|
||||
const ip = source.get('index');
|
||||
const req = new SegmentedReq(source);
|
||||
sinon.stub(ip, 'toIndexList').returns(Promise.resolve([1,2,3]));
|
||||
|
||||
req.setDirection('asc');
|
||||
await req._createQueue();
|
||||
expect(ip.toIndexList.lastCall.args[2]).to.be('asc');
|
||||
|
||||
req.setDirection('desc');
|
||||
await req._createQueue();
|
||||
expect(ip.toIndexList.lastCall.args[2]).to.be('desc');
|
||||
});
|
||||
});
|
|
@ -167,12 +167,12 @@ define(function (require) {
|
|||
SegmentedReq.prototype._createQueue = function () {
|
||||
var self = this;
|
||||
var timeBounds = timefilter.getBounds();
|
||||
var indexPattern = this.source.get('index');
|
||||
var indexPattern = self.source.get('index');
|
||||
self._queueCreated = false;
|
||||
|
||||
return indexPattern.toIndexList(timeBounds.min, timeBounds.max)
|
||||
return indexPattern.toIndexList(timeBounds.min, timeBounds.max, self._direction)
|
||||
.then(function (queue) {
|
||||
if (!_.isArray(queue)) queue = [queue];
|
||||
if (self._direction === 'desc') queue = queue.reverse();
|
||||
|
||||
self._queue = queue;
|
||||
self._queueCreated = true;
|
||||
|
|
|
@ -313,6 +313,31 @@ describe('index pattern', function () {
|
|||
expect(indexList[0]).to.equal('foo');
|
||||
expect(indexList[1]).to.equal('bar');
|
||||
});
|
||||
|
||||
context('with sort order', function () {
|
||||
require('testUtils/noDigestPromises').activateForSuite();
|
||||
|
||||
context('undefined', function () {
|
||||
it('provides the index list in tact', async function () {
|
||||
const indexList = await indexPattern.toIndexList();
|
||||
expect(indexList).to.eql(['foo', 'bar']);
|
||||
});
|
||||
});
|
||||
|
||||
context('asc', function () {
|
||||
it('provides the index list in tact', async function () {
|
||||
const indexList = await indexPattern.toIndexList(1, 2, 'asc');
|
||||
expect(indexList).to.eql(['foo', 'bar']);
|
||||
});
|
||||
});
|
||||
|
||||
context('desc', function () {
|
||||
it('reverses the index list', async function () {
|
||||
const indexList = await indexPattern.toIndexList(1, 2, 'desc');
|
||||
expect(indexList).to.eql(['bar', 'foo']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when index pattern is a time-base wildcard', function () {
|
||||
|
@ -322,13 +347,13 @@ describe('index pattern', function () {
|
|||
sinon.stub(indexPattern, 'isWildcard').returns(true);
|
||||
});
|
||||
|
||||
it('invokes calculateIndices with given start/stop times', function () {
|
||||
indexPattern.toIndexList(1, 2);
|
||||
it('invokes calculateIndices with given start/stop times and sortOrder', function () {
|
||||
indexPattern.toIndexList(1, 2, 'sortOrder');
|
||||
$rootScope.$apply();
|
||||
|
||||
var id = indexPattern.id;
|
||||
var field = indexPattern.timeFieldName;
|
||||
expect(calculateIndices.calledWith(id, field, 1, 2)).to.be(true);
|
||||
expect(calculateIndices.calledWith(id, field, 1, 2, 'sortOrder')).to.be(true);
|
||||
});
|
||||
it('is fulfilled by the result of calculateIndices', function () {
|
||||
var indexList;
|
||||
|
|
|
@ -7,37 +7,49 @@ describe('ui/index_patterns/_calculate_indices', () => {
|
|||
let Promise;
|
||||
let $rootScope;
|
||||
let calculateIndices;
|
||||
let error;
|
||||
let es;
|
||||
let response;
|
||||
let transportRequest;
|
||||
let config;
|
||||
let constraints;
|
||||
|
||||
beforeEach(ngMock.module('kibana', ($provide) => {
|
||||
error = undefined;
|
||||
response = { indices: { 'mock-*': 'irrelevant, is ignored' } };
|
||||
transportRequest = sinon.spy((options, fn) => fn(error, response));
|
||||
$provide.value('es', _.set({}, 'transport.request', transportRequest));
|
||||
response = {
|
||||
indices: {
|
||||
'mock-*': 'irrelevant, is ignored'
|
||||
}
|
||||
};
|
||||
|
||||
$provide.service('es', function (Promise) {
|
||||
return {
|
||||
fieldStats: sinon.spy(function () {
|
||||
return Promise.resolve(response);
|
||||
})
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(ngMock.inject((Private, $injector) => {
|
||||
$rootScope = $injector.get('$rootScope');
|
||||
es = $injector.get('es');
|
||||
Promise = $injector.get('Promise');
|
||||
calculateIndices = Private(require('ui/index_patterns/_calculate_indices'));
|
||||
}));
|
||||
|
||||
function run({ start = undefined, stop = undefined } = {}) {
|
||||
calculateIndices('wat-*-no', '@something', start, stop);
|
||||
$rootScope.$apply();
|
||||
config = _.first(es.fieldStats.firstCall.args);
|
||||
constraints = config.body.index_constraints;
|
||||
}
|
||||
|
||||
describe('transport configuration', () => {
|
||||
it('is POST', () => {
|
||||
it('uses pattern path for indec', () => {
|
||||
run();
|
||||
expect(config.method).to.equal('POST');
|
||||
});
|
||||
it('uses pattern path for _field_stats', () => {
|
||||
run();
|
||||
expect(config.path).to.equal('/wat-*-no/_field_stats');
|
||||
expect(config.index).to.equal('wat-*-no');
|
||||
});
|
||||
it('has level indices', () => {
|
||||
run();
|
||||
expect(config.query.level).to.equal('indices');
|
||||
expect(config.level).to.equal('indices');
|
||||
});
|
||||
it('includes time field', () => {
|
||||
run();
|
||||
|
@ -69,30 +81,55 @@ describe('ui/index_patterns/_calculate_indices', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('returned promise', () => {
|
||||
it('is rejected by transport errors', () => {
|
||||
error = 'something';
|
||||
describe('response sorting', function () {
|
||||
require('testUtils/noDigestPromises').activateForSuite();
|
||||
|
||||
let reason;
|
||||
calculateIndices('one', 'two').then(null, val => reason = val);
|
||||
$rootScope.$apply();
|
||||
context('when no sorting direction given', function () {
|
||||
it('returns the indices in the order that elasticsearch sends them', function () {
|
||||
response = {
|
||||
indices: {
|
||||
c: { fields: { time: {} } },
|
||||
a: { fields: { time: {} } },
|
||||
b: { fields: { time: {} } },
|
||||
}
|
||||
};
|
||||
|
||||
expect(reason).to.equal(error);
|
||||
return calculateIndices('*', 'time', null, null).then(function (resp) {
|
||||
expect(resp).to.eql(['c', 'a', 'b']);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('is fulfilled by array of indices in successful response', () => {
|
||||
|
||||
let indices;
|
||||
calculateIndices('one', 'two').then(val => indices = val);
|
||||
$rootScope.$apply();
|
||||
context('when sorting desc', function () {
|
||||
it('returns the indices in max_value order', function () {
|
||||
response = {
|
||||
indices: {
|
||||
c: { fields: { time: { max_value: 10 } } },
|
||||
a: { fields: { time: { max_value: 15 } } },
|
||||
b: { fields: { time: { max_value: 1 } } },
|
||||
}
|
||||
};
|
||||
|
||||
expect(_.first(indices)).to.equal('mock-*');
|
||||
return calculateIndices('*', 'time', null, null, 'desc').then(function (resp) {
|
||||
expect(resp).to.eql(['a', 'c', 'b']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('when sorting asc', function () {
|
||||
it('returns the indices in min_value order', function () {
|
||||
response = {
|
||||
indices: {
|
||||
c: { fields: { time: { max_value: 10, min_value: 9 } } },
|
||||
a: { fields: { time: { max_value: 15, min_value: 5 } } },
|
||||
b: { fields: { time: { max_value: 1, min_value: 0 } } },
|
||||
}
|
||||
};
|
||||
|
||||
return calculateIndices('*', 'time', null, null, 'asc').then(function (resp) {
|
||||
expect(resp).to.eql(['b', 'a', 'c']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function run({ start = undefined, stop = undefined } = {}) {
|
||||
calculateIndices('wat-*-no', '@something', start, stop);
|
||||
$rootScope.$apply();
|
||||
config = _.first(transportRequest.firstCall.args);
|
||||
constraints = config.body.index_constraints;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,14 +7,14 @@ define(function (require) {
|
|||
// Uses the field stats api to determine the names of indices that need to
|
||||
// be queried against that match the given pattern and fall within the
|
||||
// given time range
|
||||
function calculateIndices(...args) {
|
||||
const options = compileOptions(...args);
|
||||
return sendRequest(options);
|
||||
function calculateIndices(pattern, timeFieldName, start, stop, sortDirection) {
|
||||
return getFieldStats(pattern, timeFieldName, start, stop)
|
||||
.then(resp => sortIndexStats(resp, timeFieldName, sortDirection));
|
||||
};
|
||||
|
||||
// creates the configuration hash that must be passed to the elasticsearch
|
||||
// client
|
||||
function compileOptions(pattern, timeFieldName, start, stop) {
|
||||
function getFieldStats(pattern, timeFieldName, start, stop) {
|
||||
const constraints = {};
|
||||
if (start) {
|
||||
constraints.max_value = { gte: moment(start).valueOf() };
|
||||
|
@ -23,30 +23,32 @@ define(function (require) {
|
|||
constraints.min_value = { lte: moment(stop).valueOf() };
|
||||
}
|
||||
|
||||
return {
|
||||
method: 'POST',
|
||||
path: `/${pattern}/_field_stats`,
|
||||
query: {
|
||||
level: 'indices'
|
||||
},
|
||||
return es.fieldStats({
|
||||
index: pattern,
|
||||
level: 'indices',
|
||||
body: {
|
||||
fields: [ timeFieldName ],
|
||||
index_constraints: {
|
||||
[timeFieldName]: constraints
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// executes a request to elasticsearch with the given configuration hash
|
||||
function sendRequest(options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
es.transport.request(options, function (err, response) {
|
||||
if (err) return reject(err);
|
||||
const indices = _.map(response.indices, (info, index) => index);
|
||||
resolve(indices);
|
||||
});
|
||||
});
|
||||
function sortIndexStats(resp, timeFieldName, sortDirection) {
|
||||
if (!sortDirection) return _.keys(resp.indices);
|
||||
|
||||
// FIXME: Once https://github.com/elastic/elasticsearch/issues/14404 is closed
|
||||
// this should be sorting based on the sortable value of a field.
|
||||
const edgeKey = sortDirection === 'desc' ? 'max_value' : 'min_value';
|
||||
|
||||
return _(resp.indices)
|
||||
.map((stats, index) => (
|
||||
{ index, edge: stats.fields[timeFieldName][edgeKey] }
|
||||
))
|
||||
.sortByOrder(['edge'], [sortDirection])
|
||||
.pluck('index')
|
||||
.value();
|
||||
}
|
||||
|
||||
return calculateIndices;
|
||||
|
|
|
@ -176,15 +176,16 @@ define(function (require) {
|
|||
return this.intervalName && _.find(intervals, { name: this.intervalName });
|
||||
};
|
||||
|
||||
self.toIndexList = function (start, stop) {
|
||||
self.toIndexList = function (start, stop, sortDirection) {
|
||||
return new Promise(function (resolve) {
|
||||
var indexList;
|
||||
var interval = self.getInterval();
|
||||
|
||||
if (interval) {
|
||||
indexList = intervals.toIndexList(self.id, interval, start, stop);
|
||||
if (sortDirection === 'desc') indexList = indexList.reverse();
|
||||
} else if (self.isWildcard() && self.hasTimeField()) {
|
||||
indexList = calculateIndices(self.id, self.timeFieldName, start, stop);
|
||||
indexList = calculateIndices(self.id, self.timeFieldName, start, stop, sortDirection);
|
||||
} else {
|
||||
indexList = self.id;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue