mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[5.x] [context view] Apply filters to the context query (#11890)
Backports PR #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.
This commit is contained in:
parent
096db2792c
commit
389bcdcf44
33 changed files with 560 additions and 98 deletions
|
@ -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%"
|
||||
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
||||
fetchAnchor = Private(fetchAnchorProvider);
|
||||
}));
|
||||
|
||||
describe('function fetchAnchor', function () {
|
||||
let fetchAnchor;
|
||||
let SearchSourceStub;
|
||||
|
||||
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);
|
||||
}));
|
||||
|
||||
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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
})),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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({
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -5,7 +5,7 @@ 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 {
|
||||
|
@ -13,37 +13,43 @@ function fetchContextProvider(Private) {
|
|||
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: {},
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
|
@ -13,6 +13,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<filter-bar></filter-bar>
|
||||
|
||||
<!-- Error feedback -->
|
||||
<div
|
||||
class="kuiViewContent kuiViewContentItem"
|
||||
|
@ -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"
|
||||
|
|
|
@ -33,6 +33,7 @@ module.directive('contextApp', function ContextApp() {
|
|||
anchorUid: '=',
|
||||
columns: '=',
|
||||
indexPattern: '=',
|
||||
filters: '=',
|
||||
predecessorCount: '=',
|
||||
successorCount: '=',
|
||||
sort: '=',
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
|
||||
import uiRoutes from 'ui/routes';
|
||||
|
||||
import './app';
|
||||
|
@ -6,12 +9,12 @@ import contextAppRouteTemplate from './index.html';
|
|||
|
||||
|
||||
uiRoutes
|
||||
.when('/context/:indexPattern/:type/:id', {
|
||||
.when('/context/:indexPatternId/:type/:id', {
|
||||
controller: ContextAppRouteController,
|
||||
controllerAs: 'contextAppRoute',
|
||||
resolve: {
|
||||
indexPattern: function ($route, courier) {
|
||||
return courier.indexPatterns.get($route.current.params.indexPattern);
|
||||
return courier.indexPatterns.get($route.current.params.indexPatternId);
|
||||
},
|
||||
},
|
||||
template: contextAppRouteTemplate,
|
||||
|
@ -25,8 +28,11 @@ function ContextAppRouteController(
|
|||
chrome,
|
||||
config,
|
||||
indexPattern,
|
||||
Private,
|
||||
) {
|
||||
this.state = new AppState(createDefaultAppState(config));
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
|
||||
this.state = new AppState(createDefaultAppState(config, indexPattern));
|
||||
this.state.save(true);
|
||||
|
||||
$scope.$watchGroup([
|
||||
|
@ -34,15 +40,23 @@ function ContextAppRouteController(
|
|||
'contextAppRoute.state.predecessorCount',
|
||||
'contextAppRoute.state.successorCount',
|
||||
], () => this.state.save(true));
|
||||
|
||||
$scope.$listen(queryFilter, 'update', () => {
|
||||
this.filters = _.cloneDeep(queryFilter.getFilters());
|
||||
});
|
||||
|
||||
this.anchorUid = getDocumentUid($routeParams.type, $routeParams.id);
|
||||
this.indexPattern = indexPattern;
|
||||
this.discoverUrl = chrome.getNavLinkById('kibana:discover').lastSubUrl;
|
||||
this.filters = _.cloneDeep(queryFilter.getFilters());
|
||||
}
|
||||
|
||||
function createDefaultAppState(config) {
|
||||
function createDefaultAppState(config, indexPattern) {
|
||||
return {
|
||||
columns: ['_source'],
|
||||
filters: [],
|
||||
predecessorCount: parseInt(config.get('context:defaultSize'), 10),
|
||||
sort: [indexPattern.timeFieldName, 'desc'],
|
||||
successorCount: parseInt(config.get('context:defaultSize'), 10),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { QueryParameterActionsProvider } from '../query_parameters';
|
|||
import { LOADING_STATUS } from './constants';
|
||||
|
||||
|
||||
export function QueryActionsProvider(es, Notifier, Private, Promise) {
|
||||
export function QueryActionsProvider(courier, Notifier, Private, Promise) {
|
||||
const fetchAnchor = Private(fetchAnchorProvider);
|
||||
const { fetchPredecessors, fetchSuccessors } = Private(fetchContextProvider);
|
||||
const {
|
||||
|
@ -26,12 +26,12 @@ export function QueryActionsProvider(es, Notifier, Private, Promise) {
|
|||
);
|
||||
|
||||
const fetchAnchorRow = (state) => () => {
|
||||
const { queryParameters: { indexPattern, anchorUid, sort } } = state;
|
||||
const { queryParameters: { indexPatternId, anchorUid, sort } } = state;
|
||||
|
||||
setLoadingStatus(state)('anchor', LOADING_STATUS.LOADING);
|
||||
|
||||
return Promise.try(() => (
|
||||
fetchAnchor(indexPattern, anchorUid, _.zipObject([sort]))
|
||||
fetchAnchor(indexPatternId, anchorUid, _.zipObject([sort]))
|
||||
))
|
||||
.then(
|
||||
(anchorDocument) => {
|
||||
|
@ -49,14 +49,14 @@ export function QueryActionsProvider(es, Notifier, Private, Promise) {
|
|||
|
||||
const fetchPredecessorRows = (state) => () => {
|
||||
const {
|
||||
queryParameters: { indexPattern, predecessorCount, sort },
|
||||
queryParameters: { indexPatternId, filters, predecessorCount, sort },
|
||||
rows: { anchor },
|
||||
} = state;
|
||||
|
||||
setLoadingStatus(state)('predecessors', LOADING_STATUS.LOADING);
|
||||
|
||||
return Promise.try(() => (
|
||||
fetchPredecessors(indexPattern, anchor, _.zipObject([sort]), predecessorCount)
|
||||
fetchPredecessors(indexPatternId, anchor, _.zipObject([sort]), predecessorCount, filters)
|
||||
))
|
||||
.then(
|
||||
(predecessorDocuments) => {
|
||||
|
@ -74,14 +74,14 @@ export function QueryActionsProvider(es, Notifier, Private, Promise) {
|
|||
|
||||
const fetchSuccessorRows = (state) => () => {
|
||||
const {
|
||||
queryParameters: { indexPattern, sort, successorCount },
|
||||
queryParameters: { indexPatternId, filters, sort, successorCount },
|
||||
rows: { anchor },
|
||||
} = state;
|
||||
|
||||
setLoadingStatus(state)('successors', LOADING_STATUS.LOADING);
|
||||
|
||||
return Promise.try(() => (
|
||||
fetchSuccessors(indexPattern, anchor, _.zipObject([sort]), successorCount)
|
||||
fetchSuccessors(indexPatternId, anchor, _.zipObject([sort]), successorCount, filters)
|
||||
))
|
||||
.then(
|
||||
(successorDocuments) => {
|
||||
|
@ -97,14 +97,23 @@ export function QueryActionsProvider(es, Notifier, Private, Promise) {
|
|||
);
|
||||
};
|
||||
|
||||
const fetchContextRows = (state) => () => (
|
||||
Promise.all([
|
||||
fetchPredecessorRows(state)(),
|
||||
fetchSuccessorRows(state)(),
|
||||
])
|
||||
);
|
||||
|
||||
const fetchAllRows = (state) => () => (
|
||||
Promise.try(fetchAnchorRow(state))
|
||||
.then(() => Promise.all([
|
||||
fetchPredecessorRows(state)(),
|
||||
fetchSuccessorRows(state)(),
|
||||
]))
|
||||
.then(fetchContextRows(state))
|
||||
);
|
||||
|
||||
const fetchContextRowsWithNewQueryParameters = (state) => (queryParameters) => {
|
||||
setQueryParameters(state)(queryParameters);
|
||||
return fetchContextRows(state)();
|
||||
};
|
||||
|
||||
const fetchAllRowsWithNewQueryParameters = (state) => (queryParameters) => {
|
||||
setQueryParameters(state)(queryParameters);
|
||||
return fetchAllRows(state)();
|
||||
|
@ -142,6 +151,8 @@ export function QueryActionsProvider(es, Notifier, Private, Promise) {
|
|||
fetchAllRows,
|
||||
fetchAllRowsWithNewQueryParameters,
|
||||
fetchAnchorRow,
|
||||
fetchContextRows,
|
||||
fetchContextRowsWithNewQueryParameters,
|
||||
fetchGivenPredecessorRows,
|
||||
fetchGivenSuccessorRows,
|
||||
fetchMorePredecessorRows,
|
||||
|
|
|
@ -5,6 +5,7 @@ export function createStateStub(overrides) {
|
|||
return _.merge({
|
||||
queryParameters: {
|
||||
defaultStepSize: 3,
|
||||
indexPatternId: 'INDEX_PATTERN_ID',
|
||||
predecessorCount: 10,
|
||||
successorCount: 10,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
|
||||
import { createStateStub } from './_utils';
|
||||
import { QueryParameterActionsProvider } from '../actions';
|
||||
|
||||
|
||||
describe('context app', function () {
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
||||
describe('action addFilter', function () {
|
||||
let filterManagerStub;
|
||||
let addFilter;
|
||||
|
||||
beforeEach(ngMock.inject(function createPrivateStubs(Private) {
|
||||
filterManagerStub = createFilterManagerStub();
|
||||
Private.stub(FilterManagerProvider, filterManagerStub);
|
||||
|
||||
addFilter = Private(QueryParameterActionsProvider).addFilter;
|
||||
}));
|
||||
|
||||
it('should pass the given arguments to the filterManager', function () {
|
||||
const state = createStateStub();
|
||||
|
||||
addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION');
|
||||
|
||||
const filterManagerAddStub = filterManagerStub.add;
|
||||
expect(filterManagerAddStub.calledOnce).to.be(true);
|
||||
expect(filterManagerAddStub.firstCall.args[0]).to.eql('FIELD_NAME');
|
||||
expect(filterManagerAddStub.firstCall.args[1]).to.eql('FIELD_VALUE');
|
||||
expect(filterManagerAddStub.firstCall.args[2]).to.eql('FILTER_OPERATION');
|
||||
});
|
||||
|
||||
it('should pass the index pattern id to the filterManager', function () {
|
||||
const state = createStateStub();
|
||||
|
||||
addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION');
|
||||
|
||||
const filterManagerAddStub = filterManagerStub.add;
|
||||
expect(filterManagerAddStub.calledOnce).to.be(true);
|
||||
expect(filterManagerAddStub.firstCall.args[3]).to.eql('INDEX_PATTERN_ID');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createFilterManagerStub() {
|
||||
return {
|
||||
add: sinon.stub(),
|
||||
};
|
||||
}
|
|
@ -1,13 +1,25 @@
|
|||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
|
||||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
|
||||
import { createStateStub } from './_utils';
|
||||
import { QueryParameterActionsProvider } from '../actions';
|
||||
|
||||
|
||||
describe('context app', function () {
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
||||
describe('action increasePredecessorCount', function () {
|
||||
let increasePredecessorCount;
|
||||
|
||||
beforeEach(ngMock.inject(function createPrivateStubs(Private) {
|
||||
Private.stub(FilterManagerProvider, {});
|
||||
|
||||
increasePredecessorCount = Private(QueryParameterActionsProvider).increasePredecessorCount;
|
||||
}));
|
||||
|
||||
it('should increase the predecessorCount by the given value', function () {
|
||||
const { increasePredecessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
increasePredecessorCount(state)(20);
|
||||
|
@ -16,7 +28,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should increase the predecessorCount by the default step size if not value is given', function () {
|
||||
const { increasePredecessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
increasePredecessorCount(state)();
|
||||
|
@ -25,7 +36,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should limit the predecessorCount to 0 as a lower bound', function () {
|
||||
const { increasePredecessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
increasePredecessorCount(state)(-20);
|
||||
|
@ -34,7 +44,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should limit the predecessorCount to 10000 as an upper bound', function () {
|
||||
const { increasePredecessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
increasePredecessorCount(state)(20000);
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
|
||||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
|
||||
import { createStateStub } from './_utils';
|
||||
import { QueryParameterActionsProvider } from '../actions';
|
||||
|
||||
|
||||
describe('context app', function () {
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
||||
describe('action increaseSuccessorCount', function () {
|
||||
let increaseSuccessorCount;
|
||||
|
||||
beforeEach(ngMock.inject(function createPrivateStubs(Private) {
|
||||
Private.stub(FilterManagerProvider, {});
|
||||
|
||||
increaseSuccessorCount = Private(QueryParameterActionsProvider).increaseSuccessorCount;
|
||||
}));
|
||||
|
||||
it('should increase the successorCount by the given value', function () {
|
||||
const { increaseSuccessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
increaseSuccessorCount(state)(20);
|
||||
|
@ -16,7 +28,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should increase the successorCount by the default step size if not value is given', function () {
|
||||
const { increaseSuccessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
increaseSuccessorCount(state)();
|
||||
|
@ -25,7 +36,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should limit the successorCount to 0 as a lower bound', function () {
|
||||
const { increaseSuccessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
increaseSuccessorCount(state)(-20);
|
||||
|
@ -34,7 +44,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should limit the successorCount to 10000 as an upper bound', function () {
|
||||
const { increaseSuccessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
increaseSuccessorCount(state)(20000);
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
|
||||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
|
||||
import { createStateStub } from './_utils';
|
||||
import { QueryParameterActionsProvider } from '../actions';
|
||||
|
||||
|
||||
describe('context app', function () {
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
||||
describe('action setPredecessorCount', function () {
|
||||
let setPredecessorCount;
|
||||
|
||||
beforeEach(ngMock.inject(function createPrivateStubs(Private) {
|
||||
Private.stub(FilterManagerProvider, {});
|
||||
|
||||
setPredecessorCount = Private(QueryParameterActionsProvider).setPredecessorCount;
|
||||
}));
|
||||
|
||||
it('should set the predecessorCount to the given value', function () {
|
||||
const { setPredecessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
setPredecessorCount(state)(20);
|
||||
|
@ -16,7 +28,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should limit the predecessorCount to 0 as a lower bound', function () {
|
||||
const { setPredecessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
setPredecessorCount(state)(-1);
|
||||
|
@ -25,7 +36,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should limit the predecessorCount to 10000 as an upper bound', function () {
|
||||
const { setPredecessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
setPredecessorCount(state)(20000);
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
|
||||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
|
||||
import { createStateStub } from './_utils';
|
||||
import { QueryParameterActionsProvider } from '../actions';
|
||||
|
||||
|
||||
describe('context app', function () {
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
||||
describe('action setQueryParameters', function () {
|
||||
let setQueryParameters;
|
||||
|
||||
beforeEach(ngMock.inject(function createPrivateStubs(Private) {
|
||||
Private.stub(FilterManagerProvider, {});
|
||||
|
||||
setQueryParameters = Private(QueryParameterActionsProvider).setQueryParameters;
|
||||
}));
|
||||
|
||||
it('should update the queryParameters with valid properties from the given object', function () {
|
||||
const { setQueryParameters } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub({
|
||||
queryParameters: {
|
||||
additionalParameter: 'ADDITIONAL_PARAMETER',
|
||||
|
@ -18,7 +30,8 @@ describe('context app', function () {
|
|||
anchorUid: 'ANCHOR_UID',
|
||||
columns: ['column'],
|
||||
defaultStepSize: 3,
|
||||
indexPattern: 'INDEX_PATTERN',
|
||||
filters: ['filter'],
|
||||
indexPatternId: 'INDEX_PATTERN',
|
||||
predecessorCount: 100,
|
||||
successorCount: 100,
|
||||
sort: ['field'],
|
||||
|
@ -29,7 +42,8 @@ describe('context app', function () {
|
|||
anchorUid: 'ANCHOR_UID',
|
||||
columns: ['column'],
|
||||
defaultStepSize: 3,
|
||||
indexPattern: 'INDEX_PATTERN',
|
||||
filters: ['filter'],
|
||||
indexPatternId: 'INDEX_PATTERN',
|
||||
predecessorCount: 100,
|
||||
successorCount: 100,
|
||||
sort: ['field'],
|
||||
|
@ -37,7 +51,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should ignore invalid properties', function () {
|
||||
const { setQueryParameters } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
setQueryParameters(state)({
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
|
||||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
|
||||
import { createStateStub } from './_utils';
|
||||
import { QueryParameterActionsProvider } from '../actions';
|
||||
|
||||
|
||||
describe('context app', function () {
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
||||
describe('action setSuccessorCount', function () {
|
||||
let setSuccessorCount;
|
||||
|
||||
beforeEach(ngMock.inject(function createPrivateStubs(Private) {
|
||||
Private.stub(FilterManagerProvider, {});
|
||||
|
||||
setSuccessorCount = Private(QueryParameterActionsProvider).setSuccessorCount;
|
||||
}));
|
||||
|
||||
it('should set the successorCount to the given value', function () {
|
||||
const { setSuccessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
setSuccessorCount(state)(20);
|
||||
|
@ -16,7 +28,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should limit the successorCount to 0 as a lower bound', function () {
|
||||
const { setSuccessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
setSuccessorCount(state)(-1);
|
||||
|
@ -25,7 +36,6 @@ describe('context app', function () {
|
|||
});
|
||||
|
||||
it('should limit the successorCount to 10000 as an upper bound', function () {
|
||||
const { setSuccessorCount } = new QueryParameterActionsProvider();
|
||||
const state = createStateStub();
|
||||
|
||||
setSuccessorCount(state)(20000);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { FilterManagerProvider } from 'ui/filter_manager';
|
||||
import {
|
||||
MAX_CONTEXT_SIZE,
|
||||
MIN_CONTEXT_SIZE,
|
||||
|
@ -7,7 +8,9 @@ import {
|
|||
} from './constants';
|
||||
|
||||
|
||||
export function QueryParameterActionsProvider() {
|
||||
export function QueryParameterActionsProvider(courier, Private) {
|
||||
const filterManager = Private(FilterManagerProvider);
|
||||
|
||||
const setPredecessorCount = (state) => (predecessorCount) => (
|
||||
state.queryParameters.predecessorCount = clamp(
|
||||
MIN_CONTEXT_SIZE,
|
||||
|
@ -43,7 +46,15 @@ export function QueryParameterActionsProvider() {
|
|||
)
|
||||
);
|
||||
|
||||
const addFilter = (state) => async (field, values, operation) => {
|
||||
const indexPatternId = state.queryParameters.indexPatternId;
|
||||
filterManager.add(field, values, operation, indexPatternId);
|
||||
const indexPattern = await courier.indexPatterns.get(indexPatternId);
|
||||
indexPattern.popularizeField(field.name, 1);
|
||||
};
|
||||
|
||||
return {
|
||||
addFilter,
|
||||
increasePredecessorCount,
|
||||
increaseSuccessorCount,
|
||||
setPredecessorCount,
|
||||
|
|
|
@ -3,7 +3,8 @@ export function createInitialQueryParametersState(defaultStepSize) {
|
|||
anchorUid: null,
|
||||
columns: [],
|
||||
defaultStepSize,
|
||||
indexPattern: null,
|
||||
filters: [],
|
||||
indexPatternId: null,
|
||||
predecessorCount: 0,
|
||||
successorCount: 0,
|
||||
sort: [],
|
||||
|
|
|
@ -144,6 +144,7 @@
|
|||
columns="state.columns"
|
||||
infinite-scroll="true"
|
||||
filter="filterQuery"
|
||||
filters="state.filters"
|
||||
data-shared-item
|
||||
data-title="{{opts.savedSearch.lastSavedTitle}}"
|
||||
data-description="{{opts.savedSearch.description}}"
|
||||
|
|
|
@ -11,6 +11,8 @@ import { noWhiteSpace } from 'ui/utils/no_white_space';
|
|||
import openRowHtml from 'ui/doc_table/components/table_row/open.html';
|
||||
import detailsHtml from 'ui/doc_table/components/table_row/details.html';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { disableFilter } from 'ui/filter_bar';
|
||||
|
||||
const module = uiModules.get('app/discover');
|
||||
|
||||
|
||||
|
@ -35,6 +37,7 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl
|
|||
scope: {
|
||||
columns: '=',
|
||||
filter: '=',
|
||||
filters: '=?',
|
||||
indexPattern: '=',
|
||||
row: '=kbnTableRow',
|
||||
onAddColumn: '=?',
|
||||
|
@ -102,6 +105,7 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl
|
|||
const hash = $httpParamSerializer({
|
||||
_a: rison.encode({
|
||||
columns: $scope.columns,
|
||||
filters: ($scope.filters || []).map(disableFilter),
|
||||
}),
|
||||
});
|
||||
return `${path}?${hash}`;
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
sorting="sorting"
|
||||
index-pattern="indexPattern"
|
||||
filter="filter"
|
||||
filters="filters"
|
||||
class="discover-table-row"
|
||||
on-add-column="onAddColumn"
|
||||
on-change-sort-order="onChangeSortOrder"
|
||||
|
@ -92,6 +93,7 @@
|
|||
sorting="sorting"
|
||||
index-pattern="indexPattern"
|
||||
filter="filter"
|
||||
filters="filters"
|
||||
class="discover-table-row"
|
||||
ng-class="{'discover-table-row--highlight': row['$$_isAnchor']}"
|
||||
data-test-subj="docTableRow{{ row['$$_isAnchor'] ? ' docTableAnchorRow' : ''}}"
|
||||
|
|
|
@ -23,6 +23,7 @@ uiModules.get('kibana')
|
|||
searchSource: '=?',
|
||||
infiniteScroll: '=?',
|
||||
filter: '=?',
|
||||
filters: '=?',
|
||||
onAddColumn: '=?',
|
||||
onChangeSortOrder: '=?',
|
||||
onMoveColumn: '=?',
|
||||
|
|
|
@ -23,7 +23,12 @@
|
|||
</div>
|
||||
|
||||
<div class="filter-bar" ng-show="filters.length">
|
||||
<div class="filter" ng-class="{ negate: filter.meta.negate, disabled: filter.meta.disabled }" ng-repeat="filter in filters track by $index">
|
||||
<div
|
||||
class="filter"
|
||||
ng-class="{ negate: filter.meta.negate, disabled: filter.meta.disabled }"
|
||||
ng-repeat="filter in filters track by $index"
|
||||
data-test-subj="filter filter-{{ filter.meta.disabled ? 'disabled' : 'enabled' }} {{ filter.meta.key ? 'filter-key-' + filter.meta.key : '' }} {{ filter.meta.value ? 'filter-value-' + filter.meta.value : '' }}"
|
||||
>
|
||||
<div class="filter-description">
|
||||
<span ng-if="filter.$state.store == 'globalState'"><i class="fa fa-fw fa-thumb-tack pinned"></i></span>
|
||||
<span ng-if="filter.meta.alias">{{ filter.meta.alias }}</span>
|
||||
|
|
|
@ -10,8 +10,11 @@ import { FilterBarLibChangeTimeFilterProvider } from 'ui/filter_bar/lib/change_t
|
|||
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
|
||||
import { compareFilters } from './lib/compare_filters';
|
||||
import { uiModules } from 'ui/modules';
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
export { disableFilter, enableFilter, toggleFilterDisabled } from './lib/disable_filter';
|
||||
|
||||
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
module.directive('filterBar', function (Private, Promise, getAppState) {
|
||||
const mapAndFlattenFilters = Private(FilterBarLibMapAndFlattenFiltersProvider);
|
||||
|
|
121
src/ui/public/filter_bar/lib/__tests__/disable_filter.js
Normal file
121
src/ui/public/filter_bar/lib/__tests__/disable_filter.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
import {
|
||||
disableFilter,
|
||||
enableFilter,
|
||||
toggleFilterDisabled,
|
||||
} from '../disable_filter';
|
||||
|
||||
|
||||
describe('function disableFilter', function () {
|
||||
it('should disable a filter that is explicitly enabled', function () {
|
||||
const enabledFilter = {
|
||||
meta: {
|
||||
disabled: false,
|
||||
},
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
expect(disableFilter(enabledFilter).meta).to.have.property('disabled', true);
|
||||
});
|
||||
|
||||
it('should disable a filter that is implicitly enabled', function () {
|
||||
const enabledFilter = {
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
expect(disableFilter(enabledFilter).meta).to.have.property('disabled', true);
|
||||
});
|
||||
|
||||
it('should preserve other properties', function () {
|
||||
const enabledFilterWithProperties = {
|
||||
meta: {
|
||||
meta_property: 'META_PROPERTY',
|
||||
},
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
const disabledFilter = disableFilter(enabledFilterWithProperties);
|
||||
expect(disabledFilter).to.have.property('match_all', enabledFilterWithProperties.match_all);
|
||||
expect(disabledFilter.meta).to.have.property('meta_property', enabledFilterWithProperties.meta_property);
|
||||
});
|
||||
});
|
||||
|
||||
describe('function enableFilter', function () {
|
||||
it('should enable a filter that is disabled', function () {
|
||||
const disabledFilter = {
|
||||
meta: {
|
||||
disabled: true,
|
||||
},
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
expect(enableFilter(disabledFilter).meta).to.have.property('disabled', false);
|
||||
});
|
||||
|
||||
it('should explicitly enable a filter that is implicitly enabled', function () {
|
||||
const enabledFilter = {
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
expect(enableFilter(enabledFilter).meta).to.have.property('disabled', false);
|
||||
});
|
||||
|
||||
it('should preserve other properties', function () {
|
||||
const enabledFilterWithProperties = {
|
||||
meta: {
|
||||
meta_property: 'META_PROPERTY',
|
||||
},
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
const enabledFilter = enableFilter(enabledFilterWithProperties);
|
||||
expect(enabledFilter).to.have.property('match_all', enabledFilterWithProperties.match_all);
|
||||
expect(enabledFilter.meta).to.have.property('meta_property', enabledFilterWithProperties.meta_property);
|
||||
});
|
||||
});
|
||||
|
||||
describe('function toggleFilterDisabled', function () {
|
||||
it('should enable a filter that is disabled', function () {
|
||||
const disabledFilter = {
|
||||
meta: {
|
||||
disabled: true,
|
||||
},
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
expect(toggleFilterDisabled(disabledFilter).meta).to.have.property('disabled', false);
|
||||
});
|
||||
|
||||
it('should disable a filter that is explicitly enabled', function () {
|
||||
const enabledFilter = {
|
||||
meta: {
|
||||
disabled: false,
|
||||
},
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
expect(toggleFilterDisabled(enabledFilter).meta).to.have.property('disabled', true);
|
||||
});
|
||||
|
||||
it('should disable a filter that is implicitly enabled', function () {
|
||||
const enabledFilter = {
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
expect(toggleFilterDisabled(enabledFilter).meta).to.have.property('disabled', true);
|
||||
});
|
||||
|
||||
it('should preserve other properties', function () {
|
||||
const enabledFilterWithProperties = {
|
||||
meta: {
|
||||
meta_property: 'META_PROPERTY',
|
||||
},
|
||||
match_all: {},
|
||||
};
|
||||
|
||||
const disabledFilter = toggleFilterDisabled(enabledFilterWithProperties);
|
||||
expect(disabledFilter).to.have.property('match_all', enabledFilterWithProperties.match_all);
|
||||
expect(disabledFilter.meta).to.have.property('meta_property', enabledFilterWithProperties.meta_property);
|
||||
});
|
||||
});
|
25
src/ui/public/filter_bar/lib/disable_filter.js
Normal file
25
src/ui/public/filter_bar/lib/disable_filter.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
export function disableFilter(filter) {
|
||||
return setFilterDisabled(filter, true);
|
||||
}
|
||||
|
||||
export function enableFilter(filter) {
|
||||
return setFilterDisabled(filter, false);
|
||||
}
|
||||
|
||||
export function toggleFilterDisabled(filter) {
|
||||
const { meta: { disabled = false } = {} } = filter;
|
||||
|
||||
return setFilterDisabled(filter, !disabled);
|
||||
}
|
||||
|
||||
function setFilterDisabled(filter, disabled) {
|
||||
const { meta = {} } = filter;
|
||||
|
||||
return {
|
||||
...filter,
|
||||
meta: {
|
||||
...meta,
|
||||
disabled,
|
||||
}
|
||||
};
|
||||
}
|
|
@ -3,10 +3,12 @@ import expect from 'expect.js';
|
|||
const TEST_DISCOVER_START_TIME = '2015-09-19 06:31:44.000';
|
||||
const TEST_DISCOVER_END_TIME = '2015-09-23 18:31:44.000';
|
||||
const TEST_COLUMN_NAMES = ['@message'];
|
||||
const TEST_FILTER_COLUMN_NAMES = [['extension', 'jpg'], ['geo.src', 'IN']];
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const retry = getService('retry');
|
||||
const docTable = getService('docTable');
|
||||
const filterBar = getService('filterBar');
|
||||
const PageObjects = getPageObjects(['common', 'header', 'discover']);
|
||||
|
||||
describe('context link in discover', function contextSize() {
|
||||
|
@ -16,20 +18,26 @@ export default function ({ getService, getPageObjects }) {
|
|||
await Promise.all(TEST_COLUMN_NAMES.map((columnName) => (
|
||||
PageObjects.discover.clickFieldListItemAdd(columnName)
|
||||
)));
|
||||
await Promise.all(TEST_FILTER_COLUMN_NAMES.map(async ([columnName, value]) => {
|
||||
await PageObjects.discover.clickFieldListItem(columnName);
|
||||
await PageObjects.discover.clickFieldListPlusFilter(columnName, value);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should open the context view with the selected document as anchor', async function () {
|
||||
const discoverDocTable = await docTable.getTable();
|
||||
const firstRow = (await docTable.getBodyRows(discoverDocTable))[0];
|
||||
|
||||
// get the timestamp of the first row
|
||||
const firstTimestamp = await (await docTable.getFields(firstRow))[0]
|
||||
.getVisibleText();
|
||||
|
||||
// add a column in Discover
|
||||
// navigate to the context view
|
||||
await (await docTable.getRowExpandToggle(firstRow)).click();
|
||||
const firstDetailsRow = (await docTable.getDetailsRows(discoverDocTable))[0];
|
||||
await (await docTable.getRowActions(firstDetailsRow))[0].click();
|
||||
|
||||
// check the column in the Context View
|
||||
// check the anchor timestamp in the context view
|
||||
await retry.try(async () => {
|
||||
const contextDocTable = await docTable.getTable();
|
||||
const anchorRow = await docTable.getAnchorRow(contextDocTable);
|
||||
|
@ -52,6 +60,16 @@ export default function ({ getService, getPageObjects }) {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should open the context view with the filters disabled', async function () {
|
||||
const hasDisabledFilters = (
|
||||
await Promise.all(TEST_FILTER_COLUMN_NAMES.map(
|
||||
([columnName, value]) => filterBar.hasFilter(columnName, value, false)
|
||||
))
|
||||
).reduce((result, hasDisabledFilter) => result && hasDisabledFilter, true);
|
||||
|
||||
expect(hasDisabledFilters).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
|
62
test/functional/apps/context/_filters.js
Normal file
62
test/functional/apps/context/_filters.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
const TEST_ANCHOR_ID = 'AU_x3_BrGFA8no6QjjaI';
|
||||
const TEST_ANCHOR_TYPE = 'apache';
|
||||
const TEST_ANCHOR_FILTER_FIELD = 'geo.src';
|
||||
const TEST_ANCHOR_FILTER_VALUE = 'IN';
|
||||
const TEST_COLUMN_NAMES = ['extension', 'geo.src'];
|
||||
const TEST_INDEX_PATTERN = 'logstash-*';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const docTable = getService('docTable');
|
||||
const filterBar = getService('filterBar');
|
||||
const PageObjects = getPageObjects(['common', 'context']);
|
||||
|
||||
describe('context filters', function contextSize() {
|
||||
before(async function() {
|
||||
await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, TEST_ANCHOR_TYPE, TEST_ANCHOR_ID, {
|
||||
columns: TEST_COLUMN_NAMES,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be addable via expanded doc table rows', async function () {
|
||||
const table = await docTable.getTable();
|
||||
const anchorRow = await docTable.getAnchorRow(table);
|
||||
|
||||
await docTable.toggleRowExpanded(anchorRow);
|
||||
|
||||
const anchorDetailsRow = await docTable.getAnchorDetailsRow(table);
|
||||
await docTable.addInclusiveFilter(anchorDetailsRow, TEST_ANCHOR_FILTER_FIELD);
|
||||
await PageObjects.context.waitUntilContextLoadingHasFinished();
|
||||
|
||||
await docTable.toggleRowExpanded(anchorRow);
|
||||
|
||||
expect(await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, true)).to.be(true);
|
||||
|
||||
const rows = await docTable.getBodyRows(table);
|
||||
const hasOnlyFilteredRows = (
|
||||
await Promise.all(rows.map(
|
||||
async (row) => await (await docTable.getFields(row))[2].getVisibleText()
|
||||
))
|
||||
).every((fieldContent) => fieldContent === TEST_ANCHOR_FILTER_VALUE);
|
||||
expect(hasOnlyFilteredRows).to.be(true);
|
||||
});
|
||||
|
||||
it('should be toggleable via the filter bar', async function () {
|
||||
const table = await docTable.getTable();
|
||||
|
||||
await filterBar.toggleFilterEnabled(TEST_ANCHOR_FILTER_FIELD);
|
||||
await PageObjects.context.waitUntilContextLoadingHasFinished();
|
||||
|
||||
expect(await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, false)).to.be(true);
|
||||
|
||||
const rows = await docTable.getBodyRows(table);
|
||||
const hasOnlyFilteredRows = (
|
||||
await Promise.all(rows.map(
|
||||
async (row) => await (await docTable.getFields(row))[2].getVisibleText()
|
||||
))
|
||||
).every((fieldContent) => fieldContent === TEST_ANCHOR_FILTER_VALUE);
|
||||
expect(hasOnlyFilteredRows).to.be(false);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -22,6 +22,7 @@ export default function ({ getService, getPageObjects, loadTestFile }) {
|
|||
});
|
||||
|
||||
loadTestFile(require.resolve('./_discover_navigation'));
|
||||
loadTestFile(require.resolve('./_filters'));
|
||||
loadTestFile(require.resolve('./_size'));
|
||||
});
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
|
||||
import {
|
||||
RemoteProvider,
|
||||
FilterBarProvider,
|
||||
FindProvider,
|
||||
RetryProvider,
|
||||
TestSubjectsProvider,
|
||||
|
@ -55,6 +56,7 @@ export default async function ({ readConfigFile }) {
|
|||
esArchiver: commonConfig.get('services.esArchiver'),
|
||||
kibanaServer: commonConfig.get('services.kibanaServer'),
|
||||
remote: RemoteProvider,
|
||||
filterBar: FilterBarProvider,
|
||||
find: FindProvider,
|
||||
retry: RetryProvider,
|
||||
testSubjects: TestSubjectsProvider,
|
||||
|
|
|
@ -14,6 +14,10 @@ export function DocTableProvider({ getService }) {
|
|||
return await table.findByCssSelector('[data-test-subj~="docTableAnchorRow"]');
|
||||
}
|
||||
|
||||
async getAnchorDetailsRow(table) {
|
||||
return await table.findByCssSelector('[data-test-subj~="docTableAnchorRow"] + tr');
|
||||
}
|
||||
|
||||
async getRowExpandToggle(row) {
|
||||
return await row.findByCssSelector('[data-test-subj~="docTableExpandToggleColumn"]');
|
||||
}
|
||||
|
@ -33,6 +37,25 @@ export function DocTableProvider({ getService }) {
|
|||
async getHeaderFields(table) {
|
||||
return await table.findAllByCssSelector('[data-test-subj~="docTableHeaderField"]');
|
||||
}
|
||||
|
||||
async getTableDocViewRow(detailsRow, fieldName) {
|
||||
return await detailsRow.findByCssSelector(`[data-test-subj~="tableDocViewRow-${fieldName}"]`);
|
||||
}
|
||||
|
||||
async getAddInclusiveFilterButton(tableDocViewRow) {
|
||||
return await tableDocViewRow.findByCssSelector(`[data-test-subj~="addInclusiveFilterButton"]`);
|
||||
}
|
||||
|
||||
async addInclusiveFilter(detailsRow, fieldName) {
|
||||
const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName);
|
||||
const addInclusiveFilterButton = await this.getAddInclusiveFilterButton(tableDocViewRow);
|
||||
await addInclusiveFilterButton.click();
|
||||
}
|
||||
|
||||
async toggleRowExpanded(row) {
|
||||
const rowExpandToggle = await this.getRowExpandToggle(row);
|
||||
return await rowExpandToggle.click();
|
||||
}
|
||||
}
|
||||
|
||||
return new DocTable();
|
||||
|
|
21
test/functional/services/filter_bar.js
Normal file
21
test/functional/services/filter_bar.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
export function FilterBarProvider({ getService }) {
|
||||
const remote = getService('remote');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
class FilterBar {
|
||||
hasFilter(key, value, enabled = true) {
|
||||
const filterActivationState = enabled ? 'enabled' : 'disabled';
|
||||
return testSubjects.exists(
|
||||
`filter & filter-key-${key} & filter-value-${value} & filter-${filterActivationState}`
|
||||
);
|
||||
}
|
||||
|
||||
async toggleFilterEnabled(key) {
|
||||
const filterElement = await testSubjects.find(`filter & filter-key-${key}`);
|
||||
await remote.moveMouseTo(filterElement);
|
||||
await testSubjects.find(`filter & filter-key-${key} disableFilter-${key}`).click();
|
||||
}
|
||||
}
|
||||
|
||||
return new FilterBar();
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
export { RetryProvider } from './retry';
|
||||
export { FilterBarProvider } from './filter_bar';
|
||||
export { FindProvider } from './find';
|
||||
export { TestSubjectsProvider } from './test_subjects';
|
||||
export { RemoteProvider } from './remote';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue