Skip to content

Commit

Permalink
[5.x] [context view] Apply filters to the context query
Browse files Browse the repository at this point in the history
Backports PR elastic#11466

This adds the ability to display a filter bar in the Context view and to
apply those filters to the queries. It also modifies the link from the
Discover view to the Context view to copy the currently defined filters
when switching. New filters can be added from within the Context view
using the icons in the expanded detail rows.
  • Loading branch information
weltenwort committed May 18, 2017
1 parent 8e7b7eb commit bad761a
Show file tree
Hide file tree
Showing 33 changed files with 559 additions and 97 deletions.
3 changes: 2 additions & 1 deletion src/core_plugins/kbn_doc_views/public/views/table.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<table class="table table-condensed">
<tbody>
<tr ng-repeat="field in fields">
<tr ng-repeat="field in fields" data-test-subj="tableDocViewRow-{{ field }}">
<td field-name="field"
field-type="mapping[field].type"
width="1%"
Expand All @@ -11,6 +11,7 @@
<button
class="doc-viewer-button"
ng-click="filter(mapping[field], flattened[field], '+')"
data-test-subj="addInclusiveFilterButton"
>
<i
tooltip="Filter for value"
Expand Down
67 changes: 32 additions & 35 deletions src/core_plugins/kibana/public/context/api/__tests__/anchor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,38 @@ 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);
describe('function fetchAnchor', function () {
let fetchAnchor;
let SearchSourceStub;

fetchAnchor = Private(fetchAnchorProvider);
}));
beforeEach(ngMock.module(function createServiceStubs($provide) {
$provide.value('courier', createCourierStub());
}));

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

fetchAnchor = Private(fetchAnchorProvider);
}));

describe('function fetchAnchor', 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' })
return fetchAnchor('INDEX_PATTERN_ID', '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' })
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
.then(() => {
const inheritsSpy = searchSourceStub.inherits;
expect(inheritsSpy.calledOnce).to.be(true);
Expand All @@ -46,22 +48,20 @@ describe('context app', function () {
});

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

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

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

return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
.then(() => {
const setVersionSpy = searchSourceStub.set.withArgs('version');
expect(setVersionSpy.calledOnce).to.be(true);
Expand All @@ -70,10 +70,9 @@ describe('context app', function () {
});

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

return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
.then(() => {
const setSizeSpy = searchSourceStub.set.withArgs('size');
expect(setSizeSpy.calledOnce).to.be(true);
Expand All @@ -82,10 +81,9 @@ describe('context app', function () {
});

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' })
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
.then(() => {
const setQuerySpy = searchSourceStub.set.withArgs('query');
expect(setQuerySpy.calledOnce).to.be(true);
Expand All @@ -98,10 +96,9 @@ describe('context app', function () {
});

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

return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
.then(() => {
const setSortSpy = searchSourceStub.set.withArgs('sort');
expect(setSortSpy.calledOnce).to.be(true);
Expand All @@ -113,11 +110,10 @@ describe('context app', function () {
});

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

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

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

return fetchAnchor(indexPatternStub, 'UID', { '@timestamp': 'desc' })
return fetchAnchor('INDEX_PATTERN_ID', 'UID', { '@timestamp': 'desc' })
.then((anchorDocument) => {
expect(anchorDocument).to.have.property('property1', 'value1');
expect(anchorDocument).to.have.property('$$_isAnchor', true);
Expand All @@ -146,12 +141,13 @@ describe('context app', function () {
});


function createIndexPatternStub(indices) {
function createCourierStub() {
return {
getComputedFields: sinon.stub()
.returns({}),
toIndexList: sinon.stub()
.returns(indices),
indexPatterns: {
get: sinon.spy((indexPatternId) => Promise.resolve({
id: indexPatternId,
})),
},
};
}

Expand All @@ -160,6 +156,7 @@ function createSearchSourceStubProvider(hits) {
_stubHits: hits,
};

searchSourceStub.filter = sinon.stub().returns(searchSourceStub);
searchSourceStub.inherits = sinon.stub().returns(searchSourceStub);
searchSourceStub.set = sinon.stub().returns(searchSourceStub);
searchSourceStub.fetch = sinon.spy(() => Promise.resolve({
Expand Down
6 changes: 4 additions & 2 deletions src/core_plugins/kibana/public/context/api/anchor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import _ from 'lodash';
import { SearchSourceProvider } from 'ui/courier/data_source/search_source';


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

return async function fetchAnchor(indexPattern, uid, sort) {
return async function fetchAnchor(indexPatternId, uid, sort) {
const indexPattern = await courier.indexPatterns.get(indexPatternId);

const searchSource = new SearchSource()
.inherits(false)
.set('index', indexPattern)
Expand Down
22 changes: 14 additions & 8 deletions src/core_plugins/kibana/public/context/api/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,51 @@ import { SearchSourceProvider } from 'ui/courier/data_source/search_source';
import { reverseSortDirective } from './utils/sorting';


function fetchContextProvider(Private) {
function fetchContextProvider(courier, Private) {
const SearchSource = Private(SearchSourceProvider);

return {
fetchPredecessors,
fetchSuccessors,
};

async function fetchSuccessors(indexPattern, anchorDocument, contextSort, size) {
async function fetchSuccessors(indexPatternId, anchorDocument, contextSort, size, filters) {
const successorsSort = [contextSort, { _uid: 'asc' }];
const successorsSearchSource = createSearchSource(
indexPattern,
const successorsSearchSource = await createSearchSource(
indexPatternId,
anchorDocument,
successorsSort,
size,
filters,
);
const results = await performQuery(successorsSearchSource);
return results;
}

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

function createSearchSource(indexPattern, anchorDocument, sort, size) {
async function createSearchSource(indexPatternId, anchorDocument, sort, size, filters) {

const indexPattern = await courier.indexPatterns.get(indexPatternId);

return new SearchSource()
.inherits(false)
.set('index', indexPattern)
.set('version', true)
.set('size', size)
.set('filter', filters)
.set('query', {
match_all: {},
})
Expand Down
7 changes: 5 additions & 2 deletions src/core_plugins/kibana/public/context/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="kuiLocalNavRow">
<div class="kuiLocalNavRow__section">
<div class="kuiLocalTitle">
Surrounding Documents in {{ contextApp.state.queryParameters.indexPattern.id }}
Surrounding Documents in {{ contextApp.state.queryParameters.indexPatternId }}
</div>
</div>
</div>
Expand All @@ -13,6 +13,8 @@
</div>
</div>

<filter-bar></filter-bar>

<!-- Error feedback -->
<div
class="kuiViewContent kuiViewContentItem"
Expand Down Expand Up @@ -96,8 +98,9 @@
>
<div class="discover-table" fixed-scroll>
<doc-table
filter="contextApp.actions.addFilter"
hits="contextApp.state.rows.all"
index-pattern="contextApp.state.queryParameters.indexPattern"
index-pattern="contextApp.indexPattern"
sorting="contextApp.state.queryParameters.sort"
columns="contextApp.state.queryParameters.columns"
infinite-scroll="true"
Expand Down
38 changes: 29 additions & 9 deletions src/core_plugins/kibana/public/context/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module.directive('contextApp', function ContextApp() {
anchorUid: '=',
columns: '=',
indexPattern: '=',
filters: '=',
predecessorCount: '=',
successorCount: '=',
sort: '=',
Expand Down Expand Up @@ -71,22 +72,41 @@ function ContextAppController($scope, config, Private, timefilter) {
], (newValues) => this.actions.setAllRows(...newValues));

/**
* Sync query parameters to arguments
* Sync properties to state
*/
$scope.$watchCollection(
() => _.pick(this, QUERY_PARAMETER_KEYS),
(newValues) => {
// break the watch cycle
if (!_.isEqual(newValues, this.state.queryParameters)) {
this.actions.fetchAllRowsWithNewQueryParameters(newValues);
() => ({
...(_.pick(this, QUERY_PARAMETER_KEYS)),
indexPatternId: this.indexPattern.id,
}),
(newQueryParameters) => {
const { queryParameters } = this.state;
if (
(newQueryParameters.indexPatternId !== queryParameters.indexPatternId)
|| (newQueryParameters.anchorUid !== queryParameters.anchorUid)
|| (!_.isEqual(newQueryParameters.sort, queryParameters.sort))
) {
this.actions.fetchAllRowsWithNewQueryParameters(_.cloneDeep(newQueryParameters));
} else if (
(newQueryParameters.predecessorCount !== queryParameters.predecessorCount)
|| (newQueryParameters.successorCount !== queryParameters.successorCount)
|| (!_.isEqual(newQueryParameters.filters, queryParameters.filters))
) {
this.actions.fetchContextRowsWithNewQueryParameters(_.cloneDeep(newQueryParameters));
}
},
);

/**
* Sync state to properties
*/
$scope.$watchCollection(
() => this.state.queryParameters,
(newValues) => {
_.assign(this, newValues);
() => ({
predecessorCount: this.state.queryParameters.predecessorCount,
successorCount: this.state.queryParameters.successorCount,
}),
(newParameters) => {
_.assign(this, newParameters);
},
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/core_plugins/kibana/public/context/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
columns="contextAppRoute.state.columns"
discover-url="contextAppRoute.discoverUrl"
index-pattern="contextAppRoute.indexPattern"
filters="contextAppRoute.filters"
predecessor-count="contextAppRoute.state.predecessorCount"
successor-count="contextAppRoute.state.successorCount"
sort="[contextAppRoute.indexPattern.timeFieldName, 'desc']"
sort="contextAppRoute.state.sort"
></context-app>
Loading

0 comments on commit bad761a

Please sign in to comment.