Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[context view] Use courier when querying the context #11127

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to figure out what exactly this line accomplishes... Does the SearchSource inherit something else by default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it takes a while to trace the effect. Ultimately it disables the default inheritance from the rootSearchSource:

SearchSource.prototype.getParent = function (onlyHardLinked) {
const self = this;
if (self._parent === false) return;
if (self._parent) return self._parent;
return onlyHardLinked ? undefined : Private(rootSearchSource).get();
};

This is how the the timefilter values get mixed into the request, which I needed to disable for the context query.

.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