Skip to content

Commit

Permalink
[context view] Use courier when querying the context (#11127)
Browse files Browse the repository at this point in the history
Instead of using a separate code path for querying the anchor and
context documents, the context view actions now use a normal
`SearchSource` and thereby scripted fields, source filtering and other
features of a `SearchSource` without duplicated code.

To facilitate this the `SearchSource` gained the `searchAfter` field,
which is turned into a `search_after` clause on the request.

Additionally, the `FetchStrategyForSearch` now honors the
`timefilter.enabled` flag. When this flag is enabled, the index list
will be expanded with respect to the current time interval configured in
the `timefilter` (previous behaviour). When this flag is disabled, the
`timefilter` values will be ignored when expanding the index list.
  • Loading branch information
weltenwort authored Apr 18, 2017
1 parent b7d11c9 commit 0be7acc
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 277 deletions.
145 changes: 120 additions & 25 deletions src/core_plugins/kibana/public/context/api/__tests__/anchor.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,123 @@
import expect from 'expect.js';
import ngMock from 'ng_mock';
import sinon from 'sinon';

import { fetchAnchor } from 'plugins/kibana/context/api/anchor';
import { SearchSourceProvider } from 'ui/courier/data_source/search_source';

import { fetchAnchorProvider } from '../anchor';


describe('context app', function () {
let fetchAnchor;
let SearchSourceStub;

beforeEach(ngMock.module('kibana'));

beforeEach(ngMock.inject(function createStubs(Private) {
SearchSourceStub = createSearchSourceStubProvider([
{ _id: 'hit1' },
]);
Private.stub(SearchSourceProvider, SearchSourceStub);

fetchAnchor = Private(fetchAnchorProvider);
}));

describe('function fetchAnchor', function () {
it('should use the `search` api to query the given index', function () {
it('should use the `fetch` method of the SearchSource', function () {
const indexPatternStub = createIndexPatternStub('index1');
const searchSourceStub = new SearchSourceStub();

return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(() => {
expect(searchSourceStub.fetch.calledOnce).to.be(true);
});
});

it('should configure the SearchSource to not inherit from the implicit root', function () {
const indexPatternStub = createIndexPatternStub('index1');
const searchSourceStub = new SearchSourceStub();

return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(() => {
const inheritsSpy = searchSourceStub.inherits;
expect(inheritsSpy.calledOnce).to.be(true);
expect(inheritsSpy.firstCall.args[0]).to.eql(false);
});
});

it('should set the SearchSource index pattern', function () {
const indexPatternStub = createIndexPatternStub('index1');
const esStub = createEsStub(['hit1']);
const searchSourceStub = new SearchSourceStub();

return fetchAnchor(esStub, indexPatternStub, 'UID', { '@timestamp': 'desc' })
return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(() => {
expect(esStub.search.calledOnce).to.be(true);
expect(esStub.search.firstCall.args[0]).to.have.property('index', 'index1');
const setIndexSpy = searchSourceStub.set.withArgs('index');
expect(setIndexSpy.calledOnce).to.be(true);
expect(setIndexSpy.firstCall.args[1]).to.eql(indexPatternStub);
});
});

it('should include computed fields in the query', function () {
it('should set the SearchSource version flag to true', function () {
const indexPatternStub = createIndexPatternStub('index1');
const esStub = createEsStub(['hit1']);
const searchSourceStub = new SearchSourceStub();

return fetchAnchor(esStub, indexPatternStub, 'UID', { '@timestamp': 'desc' })
return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(() => {
expect(esStub.search.calledOnce).to.be(true);
expect(esStub.search.firstCall.args[0].body).to.have.keys([
'script_fields', 'docvalue_fields', 'stored_fields']);
const setVersionSpy = searchSourceStub.set.withArgs('version');
expect(setVersionSpy.calledOnce).to.be(true);
expect(setVersionSpy.firstCall.args[1]).to.eql(true);
});
});

it('should set the SearchSource size to 1', function () {
const indexPatternStub = createIndexPatternStub('index1');
const searchSourceStub = new SearchSourceStub();

return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(() => {
const setSizeSpy = searchSourceStub.set.withArgs('size');
expect(setSizeSpy.calledOnce).to.be(true);
expect(setSizeSpy.firstCall.args[1]).to.eql(1);
});
});

it('should set the SearchSource query to a _uid terms query', function () {
const indexPatternStub = createIndexPatternStub('index1');
const searchSourceStub = new SearchSourceStub();

return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(() => {
const setQuerySpy = searchSourceStub.set.withArgs('query');
expect(setQuerySpy.calledOnce).to.be(true);
expect(setQuerySpy.firstCall.args[1]).to.eql({
terms: {
_uid: ['UID'],
},
});
});
});

it('should set the SearchSource sort order', function () {
const indexPatternStub = createIndexPatternStub('index1');
const searchSourceStub = new SearchSourceStub();

return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(() => {
const setSortSpy = searchSourceStub.set.withArgs('sort');
expect(setSortSpy.calledOnce).to.be(true);
expect(setSortSpy.firstCall.args[1]).to.eql([
{ '@timestamp': 'desc' },
{ '_uid': 'asc' },
]);
});
});

it('should reject with an error when no hits were found', function () {
const indexPatternStub = createIndexPatternStub('index1');
const esStub = createEsStub([]);
const searchSourceStub = new SearchSourceStub();
searchSourceStub._stubHits = [];

return fetchAnchor(esStub, indexPatternStub, 'UID', { '@timestamp': 'desc' })
return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(
() => {
expect().fail('expected the promise to be rejected');
Expand All @@ -46,9 +130,13 @@ describe('context app', function () {

it('should return the first hit after adding an anchor marker', function () {
const indexPatternStub = createIndexPatternStub('index1');
const esStub = createEsStub([{ property1: 'value1' }, {}]);
const searchSourceStub = new SearchSourceStub();
searchSourceStub._stubHits = [
{ property1: 'value1' },
{ property2: 'value2' },
];

return fetchAnchor(esStub, indexPatternStub, 'UID', { '@timestamp': 'desc' })
return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then((anchorDocument) => {
expect(anchorDocument).to.have.property('property1', 'value1');
expect(anchorDocument).to.have.property('$$_isAnchor', true);
Expand All @@ -67,14 +155,21 @@ function createIndexPatternStub(indices) {
};
}

function createEsStub(hits) {
return {
search: sinon.stub()
.returns({
hits: {
hits,
total: hits.length,
},
}),
function createSearchSourceStubProvider(hits) {
const searchSourceStub = {
_stubHits: hits,
};

searchSourceStub.inherits = sinon.stub().returns(searchSourceStub);
searchSourceStub.set = sinon.stub().returns(searchSourceStub);
searchSourceStub.fetch = sinon.spy(() => Promise.resolve({
hits: {
hits: searchSourceStub._stubHits,
total: searchSourceStub._stubHits.length,
},
}));

return function SearchSourceStubProvider() {
return searchSourceStub;
};
}
58 changes: 34 additions & 24 deletions src/core_plugins/kibana/public/context/api/anchor.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
import _ from 'lodash';

import { addComputedFields } from './utils/fields';
import { createAnchorQueryBody } from './utils/queries';


async function fetchAnchor(es, indexPattern, uid, sort) {
const indices = await indexPattern.toIndexList();
const queryBody = addComputedFields(indexPattern, createAnchorQueryBody(uid, sort));
const response = await es.search({
index: indices,
body: queryBody,
});

if (_.get(response, ['hits', 'total'], 0) < 1) {
throw new Error('Failed to load anchor document.');
}

return Object.assign(
{},
response.hits.hits[0],
{
$$_isAnchor: true,
},
);
import { SearchSourceProvider } from 'ui/courier/data_source/search_source';


function fetchAnchorProvider(Private) {
const SearchSource = Private(SearchSourceProvider);

return async function fetchAnchor(indexPattern, uid, sort) {
const searchSource = new SearchSource()
.inherits(false)
.set('index', indexPattern)
.set('version', true)
.set('size', 1)
.set('query', {
terms: {
_uid: [uid],
},
})
.set('sort', [sort, { _uid: 'asc' }]);

const response = await searchSource.fetch();

if (_.get(response, ['hits', 'total'], 0) < 1) {
throw new Error('Failed to load anchor document.');
}

return Object.assign(
{},
_.get(response, ['hits', 'hits', 0]),
{
$$_isAnchor: true,
},
);
};
}


export {
fetchAnchor,
fetchAnchorProvider,
};
80 changes: 49 additions & 31 deletions src/core_plugins/kibana/public/context/api/context.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,64 @@
import _ from 'lodash';

import { addComputedFields } from './utils/fields';
import { createSuccessorsQueryBody } from './utils/queries.js';
import { reverseQuerySort } from './utils/sorting';
import { SearchSourceProvider } from 'ui/courier/data_source/search_source';

import { reverseSortDirective } from './utils/sorting';

async function fetchSuccessors(es, indexPattern, anchorDocument, sort, size) {
const successorsQueryBody = prepareQueryBody(indexPattern, anchorDocument, sort, size);
const results = await performQuery(es, indexPattern, successorsQueryBody);
return results;
}

async function fetchPredecessors(es, indexPattern, anchorDocument, sort, size) {
const successorsQueryBody = prepareQueryBody(indexPattern, anchorDocument, sort, size);
const predecessorsQueryBody = reverseQuerySort(successorsQueryBody);
const reversedResults = await performQuery(es, indexPattern, predecessorsQueryBody);
const results = reversedResults.slice().reverse();
return results;
}
function fetchContextProvider(Private) {
const SearchSource = Private(SearchSourceProvider);

return {
fetchPredecessors,
fetchSuccessors,
};

function prepareQueryBody(indexPattern, anchorDocument, sort, size) {
const successorsQueryBody = addComputedFields(
indexPattern,
createSuccessorsQueryBody(anchorDocument.sort, sort, size)
);
return successorsQueryBody;
}
async function fetchSuccessors(indexPattern, anchorDocument, contextSort, size) {
const successorsSort = [contextSort, { _uid: 'asc' }];
const successorsSearchSource = createSearchSource(
indexPattern,
anchorDocument,
successorsSort,
size,
);
const results = await performQuery(successorsSearchSource);
return results;
}

async function fetchPredecessors(indexPattern, anchorDocument, contextSort, size) {
const predecessorsSort = [reverseSortDirective(contextSort), { _uid: 'desc' }];
const predecessorsSearchSource = createSearchSource(
indexPattern,
anchorDocument,
predecessorsSort,
size,
);
const reversedResults = await performQuery(predecessorsSearchSource);
const results = reversedResults.slice().reverse();
return results;
}

async function performQuery(es, indexPattern, queryBody) {
const indices = await indexPattern.toIndexList();
function createSearchSource(indexPattern, anchorDocument, sort, size) {
return new SearchSource()
.inherits(false)
.set('index', indexPattern)
.set('version', true)
.set('size', size)
.set('query', {
match_all: {},
})
.set('searchAfter', anchorDocument.sort)
.set('sort', sort);
}

const response = await es.search({
index: indices,
body: queryBody,
});
async function performQuery(searchSource) {
const response = await searchSource.fetch();

return _.get(response, ['hits', 'hits'], []);
return _.get(response, ['hits', 'hits'], []);
}
}


export {
fetchPredecessors,
fetchSuccessors,
fetchContextProvider,
};

This file was deleted.

Loading

0 comments on commit 0be7acc

Please sign in to comment.