-
-
+
+ >
diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html
index 3e19b4dd0fc72..939f12e010bd1 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html
+++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html
@@ -37,26 +37,42 @@
-
+ >
+ ng-show="vis && rows.length !== 0"
+ style="display: flex; height: 200px"
+ >
-
+
+ filters="filters"
+ on-filters-updated="onFiltersUpdated"
+ show-query-bar="vis.type.requiresSearch && vis.type.options.showQueryBar"
+ show-filter-bar="vis.type.options.showFilterBar && chrome.getVisible()"
+ watch-depth="reference"
+ >
{
+ // The filters will automatically be set when the queryFilter emits an update event (see below)
+ queryFilter.setFilters(filters);
+ };
+
+ $scope.onCancelApplyFilters = () => {
+ $scope.state.$newFilters = [];
+ };
+
+ $scope.onApplyFilters = filters => {
+ queryFilter.addFiltersAndChangeTimeFilter(filters);
+ $scope.state.$newFilters = [];
+ };
+
+ $scope.$watch('state.$newFilters', (filters = []) => {
+ if (filters.length === 1) {
+ $scope.onApplyFilters(filters);
+ }
+ });
+
function init() {
// export some objects
$scope.savedVis = savedVis;
@@ -353,6 +376,7 @@ function VisEditor(
// update the searchSource when filters update
$scope.$listen(queryFilter, 'update', function () {
+ $scope.filters = queryFilter.getFilters();
$scope.fetch();
});
diff --git a/src/ui/public/_index.scss b/src/ui/public/_index.scss
index fc8e02aadc8da..88726b1a69e5e 100644
--- a/src/ui/public/_index.scss
+++ b/src/ui/public/_index.scss
@@ -26,6 +26,7 @@
@import './notify/index';
@import './partials/index';
@import './query_bar/index';
+@import './filter_bar/index';
@import './style_compile/index';
// The following are prefixed with "vis"
diff --git a/src/ui/public/agg_types/filter/agg_type_filters.test.ts b/src/ui/public/agg_types/filter/agg_type_filters.test.ts
index 47e7bebf9f365..416e9c1b45fd2 100644
--- a/src/ui/public/agg_types/filter/agg_type_filters.test.ts
+++ b/src/ui/public/agg_types/filter/agg_type_filters.test.ts
@@ -21,7 +21,7 @@ import { AggTypeFilters } from './agg_type_filters';
describe('AggTypeFilters', () => {
let registry: AggTypeFilters;
- const indexPattern = {};
+ const indexPattern = { id: '1234', fields: [], title: 'foo' };
const aggConfig = {};
beforeEach(() => {
diff --git a/src/ui/public/apply_filters/apply_filters_popover.tsx b/src/ui/public/apply_filters/apply_filters_popover.tsx
new file mode 100644
index 0000000000000..67f78b0951f19
--- /dev/null
+++ b/src/ui/public/apply_filters/apply_filters_popover.tsx
@@ -0,0 +1,128 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiForm,
+ EuiFormRow,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiOverlayMask,
+ EuiSwitch,
+} from '@elastic/eui';
+import { Filter } from '@kbn/es-query';
+import { FormattedMessage } from '@kbn/i18n/react';
+import React, { Component } from 'react';
+import { getFilterDisplayText } from '../filter_bar/filter_view';
+
+interface Props {
+ filters: Filter[];
+ onCancel: () => void;
+ onSubmit: (filters: Filter[]) => void;
+}
+
+interface State {
+ isFilterSelected: boolean[];
+}
+
+export class ApplyFiltersPopover extends Component {
+ public static defaultProps = {
+ filters: [],
+ };
+
+ public constructor(props: Props) {
+ super(props);
+ this.state = {
+ isFilterSelected: props.filters.map(() => true),
+ };
+ }
+
+ public render() {
+ if (this.props.filters.length === 0) {
+ return '';
+ }
+
+ const form = (
+
+ {this.props.filters.map((filter, i) => (
+
+ this.toggleFilterSelected(i)}
+ />
+
+ ))}
+
+ );
+
+ return (
+
+
+
+
+
+
+
+
+ {form}
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ private isFilterSelected = (i: number) => {
+ return this.state.isFilterSelected[i];
+ };
+
+ private toggleFilterSelected = (i: number) => {
+ const isFilterSelected = [...this.state.isFilterSelected];
+ isFilterSelected[i] = !isFilterSelected[i];
+ this.setState({ isFilterSelected });
+ };
+
+ private onSubmit = () => {
+ const selectedFilters = this.props.filters.filter(
+ (filter, i) => this.state.isFilterSelected[i]
+ );
+ this.props.onSubmit(selectedFilters);
+ };
+}
diff --git a/src/ui/public/apply_filters/directive.html b/src/ui/public/apply_filters/directive.html
new file mode 100644
index 0000000000000..ed7a5d70a2b80
--- /dev/null
+++ b/src/ui/public/apply_filters/directive.html
@@ -0,0 +1,7 @@
+
diff --git a/src/ui/public/apply_filters/directive.js b/src/ui/public/apply_filters/directive.js
new file mode 100644
index 0000000000000..d364a1843494a
--- /dev/null
+++ b/src/ui/public/apply_filters/directive.js
@@ -0,0 +1,59 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import 'ngreact';
+import { uiModules } from '../modules';
+import template from './directive.html';
+import { ApplyFiltersPopover } from './apply_filters_popover';
+import { FilterBarLibMapAndFlattenFiltersProvider } from '../filter_bar/lib/map_and_flatten_filters';
+
+const app = uiModules.get('app/kibana', ['react']);
+
+app.directive('applyFiltersPopoverComponent', (reactDirective) => {
+ return reactDirective(ApplyFiltersPopover);
+});
+
+app.directive('applyFiltersPopover', (reactDirective, Private) => {
+ const mapAndFlattenFilters = Private(FilterBarLibMapAndFlattenFiltersProvider);
+
+ return {
+ template,
+ restrict: 'E',
+ scope: {
+ filters: '=',
+ onCancel: '=',
+ onSubmit: '=',
+ },
+ link: function ($scope) {
+ $scope.state = {};
+
+ // Each time the new filters change we want to rebuild (not just re-render) the "apply filters"
+ // popover, because it has to reset its state whenever the new filters change. Setting a `key`
+ // property on the component accomplishes this due to how React handles the `key` property.
+ $scope.$watch('filters', filters => {
+ mapAndFlattenFilters(filters).then(mappedFilters => {
+ $scope.state = {
+ filters: mappedFilters,
+ key: Date.now(),
+ };
+ });
+ });
+ }
+ };
+});
diff --git a/src/ui/public/filter_bar/index.js b/src/ui/public/apply_filters/index.ts
similarity index 86%
rename from src/ui/public/filter_bar/index.js
rename to src/ui/public/apply_filters/index.ts
index 082a17b501ed9..4346316e62374 100644
--- a/src/ui/public/filter_bar/index.js
+++ b/src/ui/public/apply_filters/index.ts
@@ -17,6 +17,6 @@
* under the License.
*/
-import './filter_bar'; // directive
+import './directive';
-export { disableFilter, enableFilter, toggleFilterDisabled } from './lib/disable_filter';
+export { ApplyFiltersPopover } from './apply_filters_popover';
diff --git a/src/ui/public/doc_table/components/table_row.js b/src/ui/public/doc_table/components/table_row.js
index b7c5768bbaa9a..f16be56778355 100644
--- a/src/ui/public/doc_table/components/table_row.js
+++ b/src/ui/public/doc_table/components/table_row.js
@@ -27,7 +27,7 @@ import { noWhiteSpace } from '../../../../legacy/core_plugins/kibana/common/util
import openRowHtml from './table_row/open.html';
import detailsHtml from './table_row/details.html';
import { uiModules } from '../../modules';
-import { disableFilter } from '../../filter_bar';
+import { disableFilter } from '@kbn/es-query';
import { dispatchRenderComplete } from '../../render_complete';
const module = uiModules.get('app/discover');
diff --git a/src/ui/public/filter_bar/filter_pill/index.js b/src/ui/public/documentation_links/index.d.ts
similarity index 94%
rename from src/ui/public/filter_bar/filter_pill/index.js
rename to src/ui/public/documentation_links/index.d.ts
index 3db610588eb75..b37021b497de3 100644
--- a/src/ui/public/filter_bar/filter_pill/index.js
+++ b/src/ui/public/documentation_links/index.d.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-import './filter_pill';
+export function getDocLink(id: string): string;
diff --git a/src/ui/public/filter_bar/__tests__/filter_bar.js b/src/ui/public/filter_bar/__tests__/filter_bar.js
deleted file mode 100644
index a17875a3d8837..0000000000000
--- a/src/ui/public/filter_bar/__tests__/filter_bar.js
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import ngMock from 'ng_mock';
-import expect from 'expect.js';
-import sinon from 'sinon';
-
-import MockState from 'fixtures/mock_state';
-import $ from 'jquery';
-import '..';
-import { FilterBarLibMapFilterProvider } from '../lib/map_filter';
-import { FilterBarQueryFilterProvider } from '../query_filter';
-
-describe('Filter Bar Directive', function () {
- let $rootScope;
- let $compile;
- let Promise;
- let appState;
- let mapFilter;
- let $el;
- let $scope;
-
- beforeEach(ngMock.module('kibana/global_state', function ($provide) {
- $provide.service('getAppState', _.constant(_.constant(
- appState = new MockState({ filters: [] })
- )));
- }));
-
- beforeEach(function () {
- // load the application
- ngMock.module('kibana');
-
- ngMock.module('kibana/courier', function ($provide) {
- $provide.service('indexPatterns', require('fixtures/mock_index_patterns'));
- });
-
- ngMock.inject(function (Private, $injector, _$rootScope_, _$compile_) {
- $rootScope = _$rootScope_;
- $compile = _$compile_;
- Promise = $injector.get('Promise');
- mapFilter = Private(FilterBarLibMapFilterProvider);
-
- const queryFilter = Private(FilterBarQueryFilterProvider);
- queryFilter.getFilters = function () {
- return appState.filters;
- };
- });
- });
-
- describe('Element rendering', function () {
- beforeEach(function (done) {
- const filters = [
- { meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'apache' } } } },
- { meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'nginx' } } } },
- { meta: { index: 'logstash-*' }, exists: { field: '@timestamp' } },
- { meta: { index: 'logstash-*' }, missing: { field: 'host' }, disabled: true },
- { meta: { index: 'logstash-*', alias: 'foo' }, query: { match: { '_type': { query: 'nginx' } } } },
- ];
-
- Promise.map(filters, mapFilter).then(function (filters) {
- appState.filters = filters;
- $el = $compile(' ')($rootScope);
- $scope = $el.isolateScope();
- });
-
- const off = $rootScope.$on('filterbar:updated', function () {
- off();
- // force a nextTick so it continues *after* the $digest loop completes
- setTimeout(done, 0);
- });
-
- // kick off the digest loop
- $rootScope.$digest();
- });
-
- it('should render all the filters in state', function () {
- const filters = $el.find('.filter');
- expect(filters).to.have.length(5);
- expect($(filters[0]).find('span')[0].innerHTML).to.equal('_type:');
- expect($(filters[0]).find('span')[1].innerHTML).to.equal('"apache"');
- expect($(filters[1]).find('span')[0].innerHTML).to.equal('_type:');
- expect($(filters[1]).find('span')[1].innerHTML).to.equal('"nginx"');
- expect($(filters[2]).find('span')[0].innerHTML).to.equal('@timestamp:');
- expect($(filters[2]).find('span')[1].innerHTML).to.equal('"exists"');
- expect($(filters[3]).find('span')[0].innerHTML).to.equal('host:');
- expect($(filters[3]).find('span')[1].innerHTML).to.equal('"missing"');
- });
-
- it('should be able to set an alias', function () {
- const filter = $el.find('.filter')[4];
- expect($(filter).find('span')[0].innerHTML).to.equal('foo');
- });
-
- describe('editing filters', function () {
- beforeEach(function () {
- $scope.editFilter(appState.filters[3]);
- $scope.$digest();
- });
-
- it('should be able to edit a filter', function () {
- expect($el.find('.filter-edit-container').length).to.be(1);
- });
-
- it('should be able to stop editing a filter', function () {
- $scope.cancelEdit();
- $scope.$digest();
- expect($el.find('.filter-edit-container').length).to.be(0);
- });
-
- it('should remove old filter and add new filter when saving', function () {
- sinon.spy($scope, 'removeFilter');
- sinon.spy($scope, 'addFilters');
-
- $scope.saveEdit(appState.filters[3], appState.filters[3], false);
- expect($scope.removeFilter.called).to.be(true);
- expect($scope.addFilters.called).to.be(true);
- });
- });
-
- describe('show and hide filters', function () {
- let scope;
-
- beforeEach(() => {
- scope = $rootScope.$new();
- });
-
- function create(attrs) {
- const template = `
-
-
diff --git a/src/ui/public/filter_bar/filter_bar.js b/src/ui/public/filter_bar/filter_bar.js
deleted file mode 100644
index 0a1648ac75d4a..0000000000000
--- a/src/ui/public/filter_bar/filter_bar.js
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import template from './filter_bar.html';
-import '../directives/json_input';
-import '../filter_editor';
-import './filter_pill/filter_pill';
-import { filterAppliedAndUnwrap } from './lib/filter_applied_and_unwrap';
-import { FilterBarLibMapAndFlattenFiltersProvider } from './lib/map_and_flatten_filters';
-import { FilterBarLibMapFlattenAndWrapFiltersProvider } from './lib/map_flatten_and_wrap_filters';
-import { FilterBarLibExtractTimeFilterProvider } from './lib/extract_time_filter';
-import { FilterBarLibFilterOutTimeBasedFilterProvider } from './lib/filter_out_time_based_filter';
-import { changeTimeFilter } from './lib/change_time_filter';
-import { FilterBarQueryFilterProvider } from './query_filter';
-import { compareFilters } from './lib/compare_filters';
-import { uiModules } from '../modules';
-
-export { disableFilter, enableFilter, toggleFilterDisabled } from './lib/disable_filter';
-
-
-const module = uiModules.get('kibana');
-
-module.directive('filterBar', function (Private, Promise, getAppState) {
- const mapAndFlattenFilters = Private(FilterBarLibMapAndFlattenFiltersProvider);
- const mapFlattenAndWrapFilters = Private(FilterBarLibMapFlattenAndWrapFiltersProvider);
- const extractTimeFilter = Private(FilterBarLibExtractTimeFilterProvider);
- const filterOutTimeBasedFilter = Private(FilterBarLibFilterOutTimeBasedFilterProvider);
- const queryFilter = Private(FilterBarQueryFilterProvider);
-
- return {
- template,
- restrict: 'E',
- scope: {
- indexPatterns: '=',
- tooltipContent: '=',
- },
- link: function ($scope, $elem) {
- // bind query filter actions to the scope
- [
- 'addFilters',
- 'toggleFilter',
- 'toggleAll',
- 'pinFilter',
- 'pinAll',
- 'invertFilter',
- 'invertAll',
- 'removeFilter',
- 'removeAll'
- ].forEach(function (method) {
- $scope[method] = queryFilter[method];
- });
-
- $scope.state = getAppState();
-
- $scope.showCollapseLink = () => {
- const pill = $elem.find('filter-pill');
- return pill[pill.length - 1].offsetTop > 10;
- };
-
- $scope.filterNavToggle = {
- isOpen: true,
- tooltipContent: 'Collapse filter bar \n to show less'
- };
-
- $scope.toggleFilterShown = () => {
- const collapser = $elem.find('.filter-nav-link__collapser');
- const filterPanelPill = $elem.find('.filter-panel__pill');
- if ($scope.filterNavToggle.isOpen) {
- $scope.filterNavToggle.tooltipContent = 'Expand filter bar \n to show more';
- collapser.attr('aria-expanded', 'false');
- filterPanelPill.attr('style', 'width: calc(100% - 80px)');
- } else {
- $scope.filterNavToggle.tooltipContent = 'Collapse filter bar \n to show less';
- collapser.attr('aria-expanded', 'true');
- filterPanelPill.attr('style', 'width: auto');
- }
-
- $scope.filterNavToggle.isOpen = !$scope.filterNavToggle.isOpen;
- };
-
- $scope.applyFilters = function (filters) {
- addAndInvertFilters(filterAppliedAndUnwrap(filters));
- $scope.newFilters = [];
-
- // change time filter
- if ($scope.changeTimeFilter && $scope.changeTimeFilter.meta && $scope.changeTimeFilter.meta.apply) {
- changeTimeFilter($scope.changeTimeFilter);
- }
- };
-
- $scope.addFilter = () => {
- $scope.editingFilter = {
- meta: { isNew: true }
- };
- };
-
- $scope.deleteFilter = (filter) => {
- $scope.removeFilter(filter);
- if (filter === $scope.editingFilter) $scope.cancelEdit();
- };
-
- $scope.editFilter = (filter) => {
- $scope.editingFilter = filter;
- };
-
- $scope.cancelEdit = () => {
- delete $scope.editingFilter;
- };
-
- $scope.saveEdit = (filter, newFilter, isPinned) => {
- if (!filter.meta.isNew) $scope.removeFilter(filter);
- delete $scope.editingFilter;
- $scope.addFilters([newFilter], isPinned);
- };
-
- $scope.clearFilterBar = function () {
- $scope.newFilters = [];
- $scope.changeTimeFilter = null;
- };
-
- // update the scope filter list on filter changes
- $scope.$listen(queryFilter, 'update', function () {
- updateFilters();
- });
-
- // when appState changes, update scope's state
- $scope.$watch(getAppState, function (appState) {
- $scope.state = appState;
- });
-
- $scope.$watch('state.$newFilters', function (filters) {
- if (!filters) return;
-
- // If filters is not undefined and the length is greater than
- // one we need to set the newFilters attribute and allow the
- // users to decide what they want to apply.
- if (filters.length > 1) {
- return mapFlattenAndWrapFilters(filters)
- .then(function (results) {
- extractTimeFilter(results).then(function (filter) {
- $scope.changeTimeFilter = filter;
- });
- return results;
- })
- .then(filterOutTimeBasedFilter)
- .then(function (results) {
- $scope.newFilters = results;
- });
- }
-
- // Just add single filters to the state.
- if (filters.length === 1) {
- Promise.resolve(filters).then(function (filters) {
- extractTimeFilter(filters)
- .then(function (timeFilter) {
- if (timeFilter) changeTimeFilter(timeFilter);
- });
- return filters;
- })
- .then(filterOutTimeBasedFilter)
- .then(addAndInvertFilters);
- }
- });
-
- function addAndInvertFilters(filters) {
- const existingFilters = queryFilter.getFilters();
- const inversionFilters = _.filter(existingFilters, (existingFilter) => {
- const newMatchingFilter = _.find(filters, _.partial(compareFilters, existingFilter));
- return newMatchingFilter
- && newMatchingFilter.meta
- && existingFilter.meta
- && existingFilter.meta.negate !== newMatchingFilter.meta.negate;
- });
- const newFilters = _.reject(filters, (filter) => {
- return _.find(inversionFilters, _.partial(compareFilters, filter));
- });
-
- _.forEach(inversionFilters, $scope.invertFilter);
- $scope.addFilters(newFilters);
- }
-
- function updateFilters() {
- const filters = queryFilter.getFilters();
- mapAndFlattenFilters(filters).then(function (results) {
- // used to display the current filters in the state
- $scope.filters = _.sortBy(results, function (filter) {
- return !filter.meta.pinned;
- });
- $scope.$emit('filterbar:updated');
- });
- }
-
- updateFilters();
- }
- };
-});
diff --git a/src/ui/public/filter_bar/filter_bar.less b/src/ui/public/filter_bar/filter_bar.less
index 979ca42ea5dc1..e69de29bb2d1d 100644
--- a/src/ui/public/filter_bar/filter_bar.less
+++ b/src/ui/public/filter_bar/filter_bar.less
@@ -1,226 +0,0 @@
-// Variables ==================================================================
-@filter-bar-confirm-bg: @gray-lighter;
-@filter-bar-confirm-filter-color: @gray-darker;
-@filter-bar-confirm-border: @gray-light;
-@filter-bar-confirm-filter-bg: @gray-light;
-
-@filter-bar-bar-bg: @gray-lightest;
-@filter-bar-bar-border: @gray-lighter;
-@filter-bar-bar-condensed-bg: tint(@blue, 90%);
-
-@filter-bar-bar-filter-bg: @blue;
-@filter-bar-bar-filter-color: @white;
-@filter-bar-bar-filter-negate-bg: @brand-danger;
-@filterBarDepth: 4;
-
-filter-bar {
- z-index: @filterBarDepth !important;
-}
-
-.filter-bar-confirm {
- padding: 8px 10px 4px;
- background: @filter-bar-confirm-bg;
- border-bottom: 1px solid;
- border-bottom-color: @filter-bar-confirm-border;
-
- ul {
- margin-bottom: 0px;
- }
-
- li {
- display: inline-block;
- }
-
- li:first-child {
- font-weight: bold;
- font-size: 1.2em;
- }
-
- li button {
- font-size: 0.9em;
- padding: 2px 8px;
- }
-
- .filter {
- position: relative;
- display: inline-block;
- text-align: center;
- // Number of filter icons multiplied by icon width
- // Escaped to prevent less math
- min-width: ~"calc(5*(1.414em + 13px))";
- vertical-align: middle;
- font-size: @font-size-small;
- background-color: @filter-bar-confirm-filter-bg;
- color: @filter-bar-confirm-filter-color;
- margin-right: 4px;
- margin-bottom: 4px;
- max-width: 100%;
-
- // Replace padding with border so absolute controls position correctly
- padding: 4px 8px;
- border-radius: 12px;
- }
-}
-
-.filter-panel {
- position: relative;
-
- .filter-panel__pill {
- display: inline;
- }
-
-}
-
-.filter-panel--close {
- max-height: 36px;
- overflow-y: hidden;
- min-width: 250px;
-
- .filter-panel__pill {
- display: inline-block;
- }
-}
-
-.filter-bar {
- padding: 6px 10px 1px 10px;
- background: @filter-bar-bar-bg;
- border-bottom: solid 1px @gray-lighter;
-
- .ace_editor {
- height: 175px;
- }
-
- .filter-edit-alias {
- margin-top: 15px;
- }
-
- .filter-link {
- position: relative;
- display: inline-block;
- border: 4px solid transparent;
- margin-bottom: 4px;
- }
-
- .filter-description {
- white-space: nowrap;
- text-overflow: ellipsis;
- vertical-align: middle;
- line-height: 1.5;
- }
-
- .action-show {
- position: absolute;
- right: 30px;
- bottom: 0;
- }
-
- .filter-nav-link__icon {
- display: inline;
- position: absolute;
- top: 10px;
- right: 10px;
- opacity: 0.75;
- font-size: 16px;
-
- &:hover {
- opacity: 1;
- }
-
- .filter-nav-link__collapser {
- border: none;
- line-height: 1;
- }
- }
-
- .filter {
- position: relative;
- display: inline-block;
- text-align: center;
- // Number of filter icons multiplied by icon width
- // Escaped to prevent less math
- min-width: ~"calc(5*(1.414em + 13px))";
- font-size: @font-size-small;
- background-color: @filter-bar-bar-filter-bg;
- color: @filter-bar-bar-filter-color;
- margin-right: 4px;
- margin-bottom: 4px;
- max-width: 100%;
- vertical-align: middle;
-
- // Replace padding with border so absolute controls position correctly
- padding: 4px 8px;
- border-radius: 12px;
-
- .filter-actions {
- font-size: 1.1em;
- line-height: 1.4em;
- position: absolute;
- padding: 4px 8px;
- top: 0;
- left: 0;
- width: 100%;
- opacity: 0;
- text-align: center;
- white-space: nowrap;
- display: flex;
-
- .action {
- border: none;
- border-right: 1px solid rgba(255, 255, 255, 0.4);
- padding: 0;
- background-color: transparent;
- flex: 1 1 auto;
-
- &:last-child {
- border-right: 0;
- padding-right: 0;
- margin-right: 0;
- }
-
- .unpinned {
- .opacity(.7);
- }
-
- .fa-disabled {
- opacity: 0.7;
- cursor: not-allowed;
- }
- }
- }
-
- .filter-actions-activated {
- opacity: 1;
- }
-
- .filter-description-deactivated {
- opacity: 0.15;
- background: transparent;
- overflow: hidden;
- }
-
- &.negate {
- background-color: @filter-bar-bar-filter-negate-bg;
- }
-
- a {
- color: @filter-bar-bar-filter-color;
- }
-
- &.disabled {
- opacity: 0.6;
- background-image: repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(255,255,255,.3) 10px, rgba(255,255,255,.3) 20px);
- }
-
- &.disabled:hover {
- span {
- text-decoration: none;
- }
- }
- }
-}
-
- .filter-bar-condensed {
- padding: 6px 6px 2px 6px !important;
- font-size: 0.9em;
- background: @filter-bar-bar-condensed-bg;
- }
diff --git a/src/ui/public/filter_bar/filter_bar.tsx b/src/ui/public/filter_bar/filter_bar.tsx
new file mode 100644
index 0000000000000..551114e0bf33a
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_bar.tsx
@@ -0,0 +1,217 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui';
+import {
+ buildEmptyFilter,
+ disableFilter,
+ enableFilter,
+ Filter,
+ pinFilter,
+ toggleFilterDisabled,
+ toggleFilterNegated,
+ unpinFilter,
+} from '@kbn/es-query';
+import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
+import classNames from 'classnames';
+import React, { Component } from 'react';
+import chrome from 'ui/chrome';
+import { IndexPattern } from 'ui/index_patterns';
+import { FilterOptions } from 'ui/search_bar/components/filter_options';
+import { FilterEditor } from './filter_editor';
+import { FilterItem } from './filter_item';
+
+const config = chrome.getUiSettingsClient();
+
+interface Props {
+ filters: Filter[];
+ onFiltersUpdated: (filters: Filter[]) => void;
+ className: string;
+ indexPatterns: IndexPattern[];
+ intl: InjectedIntl;
+}
+
+interface State {
+ isAddFilterPopoverOpen: boolean;
+}
+
+class FilterBarUI extends Component {
+ public state = {
+ isAddFilterPopoverOpen: false,
+ };
+
+ public render() {
+ const classes = classNames('globalFilterBar', this.props.className);
+
+ return (
+
+
+
+
+
+
+
+ {this.renderItems()}
+ {this.renderAddFilter()}
+
+
+
+ );
+ }
+
+ private renderItems() {
+ return this.props.filters.map((filter, i) => (
+
+ this.onUpdate(i, newFilter)}
+ onRemove={() => this.onRemove(i)}
+ indexPatterns={this.props.indexPatterns}
+ />
+
+ ));
+ }
+
+ private renderAddFilter() {
+ const isPinned = config.get('filters:pinnedByDefault');
+ const [indexPattern] = this.props.indexPatterns;
+ const index = indexPattern && indexPattern.id;
+ const newFilter = buildEmptyFilter(isPinned, index);
+
+ const button = (
+
+ +{' '}
+
+
+ );
+
+ return (
+
+
+
+
+ );
+ }
+
+ private onAdd = (filter: Filter) => {
+ this.onCloseAddFilterPopover();
+ const filters = [...this.props.filters, filter];
+ this.props.onFiltersUpdated(filters);
+ };
+
+ private onRemove = (i: number) => {
+ const filters = [...this.props.filters];
+ filters.splice(i, 1);
+ this.props.onFiltersUpdated(filters);
+ };
+
+ private onUpdate = (i: number, filter: Filter) => {
+ const filters = [...this.props.filters];
+ filters[i] = filter;
+ this.props.onFiltersUpdated(filters);
+ };
+
+ private onEnableAll = () => {
+ const filters = this.props.filters.map(enableFilter);
+ this.props.onFiltersUpdated(filters);
+ };
+
+ private onDisableAll = () => {
+ const filters = this.props.filters.map(disableFilter);
+ this.props.onFiltersUpdated(filters);
+ };
+
+ private onPinAll = () => {
+ const filters = this.props.filters.map(pinFilter);
+ this.props.onFiltersUpdated(filters);
+ };
+
+ private onUnpinAll = () => {
+ const filters = this.props.filters.map(unpinFilter);
+ this.props.onFiltersUpdated(filters);
+ };
+
+ private onToggleAllNegated = () => {
+ const filters = this.props.filters.map(toggleFilterNegated);
+ this.props.onFiltersUpdated(filters);
+ };
+
+ private onToggleAllDisabled = () => {
+ const filters = this.props.filters.map(toggleFilterDisabled);
+ this.props.onFiltersUpdated(filters);
+ };
+
+ private onRemoveAll = () => {
+ this.props.onFiltersUpdated([]);
+ };
+
+ private onOpenAddFilterPopover = () => {
+ this.setState({
+ isAddFilterPopoverOpen: true,
+ });
+ };
+
+ private onCloseAddFilterPopover = () => {
+ this.setState({
+ isAddFilterPopoverOpen: false,
+ });
+ };
+}
+
+export const FilterBar = injectI18n(FilterBarUI);
diff --git a/src/ui/public/filter_bar/filter_bar_click_handler.js b/src/ui/public/filter_bar/filter_bar_click_handler.js
deleted file mode 100644
index 060be79644d38..0000000000000
--- a/src/ui/public/filter_bar/filter_bar_click_handler.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import { dedupFilters } from './lib/dedup_filters';
-import { uniqFilters } from './lib/uniq_filters';
-import { findByParam } from '../utils/find_by_param';
-import { toastNotifications } from '../notify';
-
-export function FilterBarClickHandlerProvider() {
-
- return function ($state) {
- return function (event, simulate) {
- if (!$state) return;
-
- let aggConfigResult;
-
- // Hierarchical and tabular data set their aggConfigResult parameter
- // differently because of how the point is rewritten between the two. So
- // we need to check if the point.orig is set, if not use try the point.aggConfigResult
- if (event.point.orig) {
- aggConfigResult = event.point.orig.aggConfigResult;
- } else if (event.point.values) {
- aggConfigResult = findByParam(event.point.values, 'aggConfigResult');
- } else {
- aggConfigResult = event.point.aggConfigResult;
- }
-
- if (aggConfigResult) {
- const isLegendLabel = !!event.point.values;
- let aggBuckets = _.filter(aggConfigResult.getPath(), { type: 'bucket' });
-
- // For legend clicks, use the last bucket in the path
- if (isLegendLabel) {
- // series data has multiple values, use aggConfig on the first
- // hierarchical data values is an object with the addConfig
- const aggConfig = findByParam(event.point.values, 'aggConfig');
- aggBuckets = aggBuckets.filter((result) => result.aggConfig && result.aggConfig === aggConfig);
- }
-
- let filters = _(aggBuckets)
- .map(function (result) {
- try {
- return result.createFilter();
- } catch (e) {
- if (!simulate) {
- toastNotifications.addSuccess(e.message);
- }
- }
- })
- .flatten()
- .filter(Boolean)
- .value();
-
- if (!filters.length) return;
-
- if (event.negate) {
- _.each(filters, function (filter) {
- filter.meta = filter.meta || {};
- filter.meta.negate = true;
- });
- }
-
- filters = dedupFilters($state.filters, uniqFilters(filters), { negate: true });
-
- if (!simulate) {
- $state.$newFilters = filters;
- }
- return filters;
- }
- };
- };
-}
diff --git a/src/ui/public/filter_bar/filter_editor/generic_combo_box.tsx b/src/ui/public/filter_bar/filter_editor/generic_combo_box.tsx
new file mode 100644
index 0000000000000..81b129d214e37
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/generic_combo_box.tsx
@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui';
+import React from 'react';
+
+export interface GenericComboBoxProps {
+ options: T[];
+ selectedOptions: T[];
+ getLabel: (value: T) => string;
+ onChange: (values: T[]) => void;
+ [propName: string]: any;
+}
+
+/**
+ * A generic combo box. Instead of accepting a set of options that contain a `label`, it accepts
+ * any type of object. It also accepts a `getLabel` function that each object will be sent through
+ * to get the label to be passed to the combo box. The `onChange` will trigger with the actual
+ * selected objects, rather than an option object.
+ */
+export function GenericComboBox(props: GenericComboBoxProps) {
+ const { options, selectedOptions, getLabel, onChange, ...otherProps } = props;
+
+ const labels = options.map(getLabel);
+ const euiOptions: EuiComboBoxOptionProps[] = labels.map(label => ({ label }));
+ const selectedEuiOptions = selectedOptions.map(option => {
+ return euiOptions[options.indexOf(option)];
+ });
+
+ const onComboBoxChange = (newOptions: EuiComboBoxOptionProps[]) => {
+ const newValues = newOptions.map(({ label }) => {
+ return options[labels.indexOf(label)];
+ });
+ onChange(newValues);
+ };
+
+ return (
+
+ );
+}
diff --git a/src/ui/public/filter_bar/filter_editor/index.tsx b/src/ui/public/filter_bar/filter_editor/index.tsx
new file mode 100644
index 0000000000000..de301432b1b67
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/index.tsx
@@ -0,0 +1,471 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ // @ts-ignore
+ EuiCodeEditor,
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiForm,
+ EuiFormRow,
+ EuiPopoverTitle,
+ EuiSpacer,
+ EuiSwitch,
+} from '@elastic/eui';
+import { FieldFilter, Filter } from '@kbn/es-query';
+import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
+import { get } from 'lodash';
+import React, { Component } from 'react';
+import { Field, IndexPattern } from 'ui/index_patterns';
+import { GenericComboBox, GenericComboBoxProps } from './generic_combo_box';
+import {
+ buildCustomFilter,
+ buildFilter,
+ getFieldFromFilter,
+ getFilterableFields,
+ getFilterParams,
+ getIndexPatternFromFilter,
+ getOperatorFromFilter,
+ getOperatorOptions,
+ getQueryDslFromFilter,
+ isFilterValid,
+} from './lib/filter_editor_utils';
+import { Operator } from './lib/filter_operators';
+import { PhraseValueInput } from './phrase_value_input';
+import { PhrasesValuesInput } from './phrases_values_input';
+import { RangeValueInput } from './range_value_input';
+
+interface Props {
+ filter: Filter;
+ indexPatterns: IndexPattern[];
+ onSubmit: (filter: Filter) => void;
+ onCancel: () => void;
+ intl: InjectedIntl;
+}
+
+interface State {
+ selectedIndexPattern?: IndexPattern;
+ selectedField?: Field;
+ selectedOperator?: Operator;
+ params: any;
+ useCustomLabel: boolean;
+ customLabel: string | null;
+ queryDsl: string;
+ isCustomEditorOpen: boolean;
+}
+
+class FilterEditorUI extends Component {
+ public constructor(props: Props) {
+ super(props);
+ this.state = {
+ selectedIndexPattern: this.getIndexPatternFromFilter(),
+ selectedField: this.getFieldFromFilter(),
+ selectedOperator: this.getSelectedOperator(),
+ params: getFilterParams(props.filter),
+ useCustomLabel: props.filter.meta.alias !== null,
+ customLabel: props.filter.meta.alias,
+ queryDsl: JSON.stringify(getQueryDslFromFilter(props.filter), null, 2),
+ isCustomEditorOpen: this.isUnknownFilterType(),
+ };
+ }
+
+ public render() {
+ return (
+
+
+
+ indexPattern.title}
+ onChange={this.onIndexPatternChange}
+ singleSelection={{ asPlainText: true }}
+ isClearable={false}
+ />
+
+
+
+ );
+ }
+
+ private renderRegularEditor() {
+ return (
+
+ field.name}
+ onChange={this.onFieldChange}
+ singleSelection={{ asPlainText: true }}
+ isClearable={false}
+ data-test-subj="filterFieldSuggestionList"
+ />
+
+ );
+ }
+
+ private renderOperatorInput() {
+ const { selectedField, selectedOperator } = this.state;
+ const operators = selectedField ? getOperatorOptions(selectedField) : [];
+ return (
+
+ message}
+ onChange={this.onOperatorChange}
+ singleSelection={{ asPlainText: true }}
+ isClearable={false}
+ data-test-subj="filterOperatorList"
+ />
+
+ );
+ }
+
+ private renderCustomEditor() {
+ return (
+
+
+
+ );
+ }
+
+ private renderParamsEditor() {
+ const indexPattern = this.state.selectedIndexPattern;
+ if (!indexPattern || !this.state.selectedOperator) {
+ return '';
+ }
+
+ switch (this.state.selectedOperator.type) {
+ case 'exists':
+ return '';
+ case 'phrase':
+ return (
+
+ );
+ case 'phrases':
+ return (
+
+ );
+ case 'range':
+ return (
+
+ );
+ }
+ }
+
+ private toggleCustomEditor = () => {
+ const isCustomEditorOpen = !this.state.isCustomEditorOpen;
+ this.setState({ isCustomEditorOpen });
+ };
+
+ private isUnknownFilterType() {
+ const { type } = this.props.filter.meta;
+ return !!type && !['phrase', 'phrases', 'range', 'exists'].includes(type);
+ }
+
+ private getIndexPatternFromFilter() {
+ return getIndexPatternFromFilter(this.props.filter, this.props.indexPatterns);
+ }
+
+ private getFieldFromFilter() {
+ const indexPattern = this.getIndexPatternFromFilter();
+ return indexPattern && getFieldFromFilter(this.props.filter as FieldFilter, indexPattern);
+ }
+
+ private getSelectedOperator() {
+ return getOperatorFromFilter(this.props.filter);
+ }
+
+ private isFilterValid() {
+ const {
+ isCustomEditorOpen,
+ queryDsl,
+ selectedIndexPattern: indexPattern,
+ selectedField: field,
+ selectedOperator: operator,
+ params,
+ } = this.state;
+
+ if (isCustomEditorOpen) {
+ try {
+ return Boolean(JSON.parse(queryDsl));
+ } catch (e) {
+ return false;
+ }
+ }
+
+ return isFilterValid(indexPattern, field, operator, params);
+ }
+
+ private onIndexPatternChange = ([selectedIndexPattern]: IndexPattern[]) => {
+ const selectedField = undefined;
+ const selectedOperator = undefined;
+ const params = undefined;
+ this.setState({ selectedIndexPattern, selectedField, selectedOperator, params });
+ };
+
+ private onFieldChange = ([selectedField]: Field[]) => {
+ const selectedOperator = undefined;
+ const params = undefined;
+ this.setState({ selectedField, selectedOperator, params });
+ };
+
+ private onOperatorChange = ([selectedOperator]: Operator[]) => {
+ // Only reset params when the operator type changes
+ const params =
+ get(this.state.selectedOperator, 'type') === get(selectedOperator, 'type')
+ ? this.state.params
+ : undefined;
+ this.setState({ selectedOperator, params });
+ };
+
+ private onCustomLabelSwitchChange = (event: React.ChangeEvent) => {
+ const useCustomLabel = event.target.checked;
+ const customLabel = event.target.checked ? '' : null;
+ this.setState({ useCustomLabel, customLabel });
+ };
+
+ private onCustomLabelChange = (event: React.ChangeEvent) => {
+ const customLabel = event.target.value;
+ this.setState({ customLabel });
+ };
+
+ private onParamsChange = (params: any) => {
+ this.setState({ params });
+ };
+
+ private onQueryDslChange = (queryDsl: string) => {
+ this.setState({ queryDsl });
+ };
+
+ private onSubmit = () => {
+ const {
+ selectedIndexPattern: indexPattern,
+ selectedField: field,
+ selectedOperator: operator,
+ params,
+ useCustomLabel,
+ customLabel,
+ isCustomEditorOpen,
+ queryDsl,
+ } = this.state;
+
+ const { store } = this.props.filter.$state;
+ const alias = useCustomLabel ? customLabel : null;
+
+ if (isCustomEditorOpen) {
+ const { index, disabled, negate } = this.props.filter.meta;
+ const newIndex = index || this.props.indexPatterns[0].id;
+ const body = JSON.parse(queryDsl);
+ const filter = buildCustomFilter(newIndex, body, disabled, negate, alias, store);
+ this.props.onSubmit(filter);
+ } else if (indexPattern && field && operator) {
+ const filter = buildFilter(indexPattern, field, operator, params, alias, store);
+ this.props.onSubmit(filter);
+ }
+ };
+}
+
+function IndexPatternComboBox(props: GenericComboBoxProps) {
+ return GenericComboBox(props);
+}
+
+function FieldComboBox(props: GenericComboBoxProps) {
+ return GenericComboBox(props);
+}
+
+function OperatorComboBox(props: GenericComboBoxProps) {
+ return GenericComboBox(props);
+}
+
+export const FilterEditor = injectI18n(FilterEditorUI);
diff --git a/src/ui/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts b/src/ui/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts
new file mode 100644
index 0000000000000..859f2fcc1535a
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/lib/filter_editor_utils.test.ts
@@ -0,0 +1,314 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { FilterStateStore, toggleFilterNegated } from '@kbn/es-query';
+import { mockFields, mockIndexPattern } from 'ui/index_patterns/fixtures';
+import {
+ buildFilter,
+ getFieldFromFilter,
+ getFilterableFields,
+ getFilterParams,
+ getIndexPatternFromFilter,
+ getOperatorFromFilter,
+ getOperatorOptions,
+ getQueryDslFromFilter,
+ isFilterValid,
+} from './filter_editor_utils';
+import {
+ doesNotExistOperator,
+ existsOperator,
+ isBetweenOperator,
+ isOneOfOperator,
+ isOperator,
+} from './filter_operators';
+import { existsFilter } from './fixtures/exists_filter';
+import { phraseFilter } from './fixtures/phrase_filter';
+import { phrasesFilter } from './fixtures/phrases_filter';
+import { rangeFilter } from './fixtures/range_filter';
+
+describe('Filter editor utils', () => {
+ describe('getQueryDslFromFilter', () => {
+ it('should return query DSL without meta and $state', () => {
+ const queryDsl = getQueryDslFromFilter(phraseFilter);
+ expect(queryDsl).not.toHaveProperty('meta');
+ expect(queryDsl).not.toHaveProperty('$state');
+ });
+ });
+
+ describe('getIndexPatternFromFilter', () => {
+ it('should return the index pattern from the filter', () => {
+ const indexPattern = getIndexPatternFromFilter(phraseFilter, [mockIndexPattern]);
+ expect(indexPattern).toBe(mockIndexPattern);
+ });
+ });
+
+ describe('getFieldFromFilter', () => {
+ it('should return the field from the filter', () => {
+ const field = getFieldFromFilter(phraseFilter, mockIndexPattern);
+ expect(field).not.toBeUndefined();
+ expect(field && field.name).toBe(phraseFilter.meta.key);
+ });
+ });
+
+ describe('getOperatorFromFilter', () => {
+ it('should return "is" for phrase filter', () => {
+ const operator = getOperatorFromFilter(phraseFilter);
+ expect(operator).not.toBeUndefined();
+ expect(operator && operator.type).toBe('phrase');
+ expect(operator && operator.negate).toBe(false);
+ });
+
+ it('should return "is not" for phrase filter', () => {
+ const negatedPhraseFilter = toggleFilterNegated(phraseFilter);
+ const operator = getOperatorFromFilter(negatedPhraseFilter);
+ expect(operator).not.toBeUndefined();
+ expect(operator && operator.type).toBe('phrase');
+ expect(operator && operator.negate).toBe(true);
+ });
+
+ it('should return "is one of" for phrases filter', () => {
+ const operator = getOperatorFromFilter(phrasesFilter);
+ expect(operator).not.toBeUndefined();
+ expect(operator && operator.type).toBe('phrases');
+ expect(operator && operator.negate).toBe(false);
+ });
+
+ it('should return "is not one of" for negated phrases filter', () => {
+ const negatedPhrasesFilter = toggleFilterNegated(phrasesFilter);
+ const operator = getOperatorFromFilter(negatedPhrasesFilter);
+ expect(operator).not.toBeUndefined();
+ expect(operator && operator.type).toBe('phrases');
+ expect(operator && operator.negate).toBe(true);
+ });
+
+ it('should return "is between" for range filter', () => {
+ const operator = getOperatorFromFilter(rangeFilter);
+ expect(operator).not.toBeUndefined();
+ expect(operator && operator.type).toBe('range');
+ expect(operator && operator.negate).toBe(false);
+ });
+
+ it('should return "is not between" for negated range filter', () => {
+ const negatedRangeFilter = toggleFilterNegated(rangeFilter);
+ const operator = getOperatorFromFilter(negatedRangeFilter);
+ expect(operator).not.toBeUndefined();
+ expect(operator && operator.type).toBe('range');
+ expect(operator && operator.negate).toBe(true);
+ });
+
+ it('should return "exists" for exists filter', () => {
+ const operator = getOperatorFromFilter(existsFilter);
+ expect(operator).not.toBeUndefined();
+ expect(operator && operator.type).toBe('exists');
+ expect(operator && operator.negate).toBe(false);
+ });
+
+ it('should return "does not exists" for negated exists filter', () => {
+ const negatedExistsFilter = toggleFilterNegated(existsFilter);
+ const operator = getOperatorFromFilter(negatedExistsFilter);
+ expect(operator).not.toBeUndefined();
+ expect(operator && operator.type).toBe('exists');
+ expect(operator && operator.negate).toBe(true);
+ });
+ });
+
+ describe('getFilterParams', () => {
+ it('should retrieve params from phrase filter', () => {
+ const params = getFilterParams(phraseFilter);
+ expect(params).toBe('ios');
+ });
+
+ it('should retrieve params from phrases filter', () => {
+ const params = getFilterParams(phrasesFilter);
+ expect(params).toEqual(['win xp', 'osx']);
+ });
+
+ it('should retrieve params from range filter', () => {
+ const params = getFilterParams(rangeFilter);
+ expect(params).toEqual({ from: 0, to: 10 });
+ });
+
+ it('should return undefined for exists filter', () => {
+ const params = getFilterParams(existsFilter);
+ expect(params).toBeUndefined();
+ });
+ });
+
+ describe('getFilterableFields', () => {
+ it('returns the list of fields from the given index pattern', () => {
+ const fieldOptions = getFilterableFields(mockIndexPattern);
+ expect(fieldOptions.length).toBeGreaterThan(0);
+ });
+
+ it('limits the fields to the filterable fields', () => {
+ const fieldOptions = getFilterableFields(mockIndexPattern);
+ const nonFilterableFields = fieldOptions.filter(field => !field.filterable);
+ expect(nonFilterableFields.length).toBe(0);
+ });
+ });
+
+ describe('getOperatorOptions', () => {
+ it('returns range for number fields', () => {
+ const [field] = mockFields.filter(({ type }) => type === 'number');
+ const operatorOptions = getOperatorOptions(field);
+ const rangeOperator = operatorOptions.find(operator => operator.type === 'range');
+ expect(rangeOperator).not.toBeUndefined();
+ });
+
+ it('does not return range for string fields', () => {
+ const [field] = mockFields.filter(({ type }) => type === 'string');
+ const operatorOptions = getOperatorOptions(field);
+ const rangeOperator = operatorOptions.find(operator => operator.type === 'range');
+ expect(rangeOperator).toBeUndefined();
+ });
+ });
+
+ describe('isFilterValid', () => {
+ it('should return false if index pattern is not provided', () => {
+ const isValid = isFilterValid(undefined, mockFields[0], isOperator, 'foo');
+ expect(isValid).toBe(false);
+ });
+
+ it('should return false if field is not provided', () => {
+ const isValid = isFilterValid(mockIndexPattern, undefined, isOperator, 'foo');
+ expect(isValid).toBe(false);
+ });
+
+ it('should return false if operator is not provided', () => {
+ const isValid = isFilterValid(mockIndexPattern, mockFields[0], undefined, 'foo');
+ expect(isValid).toBe(false);
+ });
+
+ it('should return false for phrases filter without phrases', () => {
+ const isValid = isFilterValid(mockIndexPattern, mockFields[0], isOneOfOperator, []);
+ expect(isValid).toBe(false);
+ });
+
+ it('should return true for phrases filter with phrases', () => {
+ const isValid = isFilterValid(mockIndexPattern, mockFields[0], isOneOfOperator, ['foo']);
+ expect(isValid).toBe(true);
+ });
+
+ it('should return false for range filter without range', () => {
+ const isValid = isFilterValid(mockIndexPattern, mockFields[0], isBetweenOperator, undefined);
+ expect(isValid).toBe(false);
+ });
+
+ it('should return true for range filter with from', () => {
+ const isValid = isFilterValid(mockIndexPattern, mockFields[0], isBetweenOperator, {
+ from: 'foo',
+ });
+ expect(isValid).toBe(true);
+ });
+
+ it('should return true for range filter with from/to', () => {
+ const isValid = isFilterValid(mockIndexPattern, mockFields[0], isBetweenOperator, {
+ from: 'foo',
+ too: 'goo',
+ });
+ expect(isValid).toBe(true);
+ });
+
+ it('should return true for exists filter without params', () => {
+ const isValid = isFilterValid(mockIndexPattern, mockFields[0], existsOperator);
+ expect(isValid).toBe(true);
+ });
+ });
+
+ describe('buildFilter', () => {
+ it('should build phrase filters', () => {
+ const params = 'foo';
+ const alias = 'bar';
+ const state = FilterStateStore.APP_STATE;
+ const filter = buildFilter(mockIndexPattern, mockFields[0], isOperator, params, alias, state);
+ expect(filter.meta.negate).toBe(isOperator.negate);
+ expect(filter.meta.alias).toBe(alias);
+ expect(filter.$state.store).toBe(state);
+ });
+
+ it('should build phrases filters', () => {
+ const params = ['foo', 'bar'];
+ const alias = 'bar';
+ const state = FilterStateStore.APP_STATE;
+ const filter = buildFilter(
+ mockIndexPattern,
+ mockFields[0],
+ isOneOfOperator,
+ params,
+ alias,
+ state
+ );
+ expect(filter.meta.type).toBe(isOneOfOperator.type);
+ expect(filter.meta.negate).toBe(isOneOfOperator.negate);
+ expect(filter.meta.alias).toBe(alias);
+ expect(filter.$state.store).toBe(state);
+ });
+
+ it('should build range filters', () => {
+ const params = { from: 'foo', to: 'qux' };
+ const alias = 'bar';
+ const state = FilterStateStore.APP_STATE;
+ const filter = buildFilter(
+ mockIndexPattern,
+ mockFields[0],
+ isBetweenOperator,
+ params,
+ alias,
+ state
+ );
+ expect(filter.meta.negate).toBe(isBetweenOperator.negate);
+ expect(filter.meta.alias).toBe(alias);
+ expect(filter.$state.store).toBe(state);
+ });
+
+ it('should build exists filters', () => {
+ const params = undefined;
+ const alias = 'bar';
+ const state = FilterStateStore.APP_STATE;
+ const filter = buildFilter(
+ mockIndexPattern,
+ mockFields[0],
+ existsOperator,
+ params,
+ alias,
+ state
+ );
+ expect(filter.meta.negate).toBe(existsOperator.negate);
+ expect(filter.meta.alias).toBe(alias);
+ expect(filter.$state.store).toBe(state);
+ });
+
+ it('should negate based on operator', () => {
+ const params = undefined;
+ const alias = 'bar';
+ const state = FilterStateStore.APP_STATE;
+ const filter = buildFilter(
+ mockIndexPattern,
+ mockFields[0],
+ doesNotExistOperator,
+ params,
+ alias,
+ state
+ );
+ expect(filter.meta.negate).toBe(doesNotExistOperator.negate);
+ expect(filter.meta.alias).toBe(alias);
+ expect(filter.$state.store).toBe(state);
+ });
+ });
+});
diff --git a/src/ui/public/filter_bar/filter_editor/lib/filter_editor_utils.ts b/src/ui/public/filter_bar/filter_editor/lib/filter_editor_utils.ts
new file mode 100644
index 0000000000000..b1b456e482ac9
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/lib/filter_editor_utils.ts
@@ -0,0 +1,178 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import dateMath from '@elastic/datemath';
+import {
+ buildExistsFilter,
+ buildPhraseFilter,
+ buildPhrasesFilter,
+ buildRangeFilter,
+ FieldFilter,
+ Filter,
+ FilterMeta,
+ FilterStateStore,
+ PhraseFilter,
+ PhrasesFilter,
+ RangeFilter,
+} from '@kbn/es-query';
+import { omit } from 'lodash';
+import { Field, IndexPattern } from 'ui/index_patterns';
+import { isFilterable } from 'ui/index_patterns/static_utils';
+import Ipv4Address from 'ui/utils/ipv4_address';
+import { FILTER_OPERATORS, Operator } from './filter_operators';
+
+export function getIndexPatternFromFilter(
+ filter: Filter,
+ indexPatterns: IndexPattern[]
+): IndexPattern | undefined {
+ return indexPatterns.find(indexPattern => indexPattern.id === filter.meta.index);
+}
+
+export function getFieldFromFilter(filter: FieldFilter, indexPattern: IndexPattern) {
+ return indexPattern.fields.find(field => field.name === filter.meta.key);
+}
+
+export function getOperatorFromFilter(filter: Filter) {
+ return FILTER_OPERATORS.find(operator => {
+ return filter.meta.type === operator.type && filter.meta.negate === operator.negate;
+ });
+}
+
+export function getQueryDslFromFilter(filter: Filter) {
+ return omit(filter, ['$state', 'meta']);
+}
+
+export function getFilterableFields(indexPattern: IndexPattern) {
+ return indexPattern.fields.filter(isFilterable);
+}
+
+export function getOperatorOptions(field: Field) {
+ return FILTER_OPERATORS.filter(operator => {
+ return !operator.fieldTypes || operator.fieldTypes.includes(field.type);
+ });
+}
+
+export function getFilterParams(filter: Filter) {
+ switch (filter.meta.type) {
+ case 'phrase':
+ return (filter as PhraseFilter).meta.params.query;
+ case 'phrases':
+ return (filter as PhrasesFilter).meta.params;
+ case 'range':
+ return {
+ from: (filter as RangeFilter).meta.params.gte,
+ to: (filter as RangeFilter).meta.params.lt,
+ };
+ }
+}
+
+export function validateParams(params: any, type: string) {
+ switch (type) {
+ case 'date':
+ const moment = typeof params === 'string' ? dateMath.parse(params) : null;
+ return Boolean(typeof params === 'string' && moment && moment.isValid());
+ case 'ip':
+ try {
+ return Boolean(new Ipv4Address(params));
+ } catch (e) {
+ return false;
+ }
+ default:
+ return true;
+ }
+}
+
+export function isFilterValid(
+ indexPattern?: IndexPattern,
+ field?: Field,
+ operator?: Operator,
+ params?: any
+) {
+ if (!indexPattern || !field || !operator) {
+ return false;
+ }
+ switch (operator.type) {
+ case 'phrase':
+ return validateParams(params, field.type);
+ case 'phrases':
+ if (!Array.isArray(params) || !params.length) {
+ return false;
+ }
+ return params.every(phrase => validateParams(phrase, field.type));
+ case 'range':
+ if (typeof params !== 'object') {
+ return false;
+ }
+ return validateParams(params.from, field.type) || validateParams(params.to, field.type);
+ case 'exists':
+ return true;
+ default:
+ throw new Error(`Unknown operator type: ${operator.type}`);
+ }
+}
+
+export function buildFilter(
+ indexPattern: IndexPattern,
+ field: Field,
+ operator: Operator,
+ params: any,
+ alias: string | null,
+ store: FilterStateStore
+): Filter {
+ const filter = buildBaseFilter(indexPattern, field, operator, params);
+ filter.meta.alias = alias;
+ filter.meta.negate = operator.negate;
+ filter.$state = { store };
+ return filter;
+}
+
+function buildBaseFilter(
+ indexPattern: IndexPattern,
+ field: Field,
+ operator: Operator,
+ params: any
+): Filter {
+ switch (operator.type) {
+ case 'phrase':
+ return buildPhraseFilter(field, params, indexPattern);
+ case 'phrases':
+ return buildPhrasesFilter(field, params, indexPattern);
+ case 'range':
+ const newParams = { gte: params.from, lt: params.to };
+ return buildRangeFilter(field, newParams, indexPattern);
+ case 'exists':
+ return buildExistsFilter(field, indexPattern);
+ default:
+ throw new Error(`Unknown operator type: ${operator.type}`);
+ }
+}
+
+export function buildCustomFilter(
+ index: string,
+ queryDsl: any,
+ disabled: boolean,
+ negate: boolean,
+ alias: string | null,
+ store: FilterStateStore
+): Filter {
+ const meta: FilterMeta = { index, type: 'custom', disabled, negate, alias };
+ const filter: Filter = { ...queryDsl, meta };
+ filter.$state = { store };
+ return filter;
+}
diff --git a/src/ui/public/filter_bar/filter_editor/lib/filter_operators.ts b/src/ui/public/filter_bar/filter_editor/lib/filter_operators.ts
new file mode 100644
index 0000000000000..b97f4a73901b6
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/lib/filter_operators.ts
@@ -0,0 +1,106 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export interface Operator {
+ message: string;
+ type: string;
+ negate: boolean;
+ fieldTypes?: string[];
+}
+
+export const isOperator = {
+ message: i18n.translate('common.ui.filterEditor.isOperatorOptionLabel', {
+ defaultMessage: 'is',
+ }),
+ type: 'phrase',
+ negate: false,
+};
+
+export const isNotOperator = {
+ message: i18n.translate('common.ui.filterEditor.isNotOperatorOptionLabel', {
+ defaultMessage: 'is not',
+ }),
+ type: 'phrase',
+ negate: true,
+};
+
+export const isOneOfOperator = {
+ message: i18n.translate('common.ui.filterEditor.isOneOfOperatorOptionLabel', {
+ defaultMessage: 'is one of',
+ }),
+ type: 'phrases',
+ negate: false,
+ fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'],
+};
+
+export const isNotOneOfOperator = {
+ message: i18n.translate('common.ui.filterEditor.isNotOneOfOperatorOptionLabel', {
+ defaultMessage: 'is not one of',
+ }),
+ type: 'phrases',
+ negate: true,
+ fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'],
+};
+
+export const isBetweenOperator = {
+ message: i18n.translate('common.ui.filterEditor.isBetweenOperatorOptionLabel', {
+ defaultMessage: 'is between',
+ }),
+ type: 'range',
+ negate: false,
+ fieldTypes: ['number', 'date', 'ip'],
+};
+
+export const isNotBetweenOperator = {
+ message: i18n.translate('common.ui.filterEditor.isNotBetweenOperatorOptionLabel', {
+ defaultMessage: 'is not between',
+ }),
+ type: 'range',
+ negate: true,
+ fieldTypes: ['number', 'date', 'ip'],
+};
+
+export const existsOperator = {
+ message: i18n.translate('common.ui.filterEditor.existsOperatorOptionLabel', {
+ defaultMessage: 'exists',
+ }),
+ type: 'exists',
+ negate: false,
+};
+
+export const doesNotExistOperator = {
+ message: i18n.translate('common.ui.filterEditor.doesNotExistOperatorOptionLabel', {
+ defaultMessage: 'does not exist',
+ }),
+ type: 'exists',
+ negate: true,
+};
+
+export const FILTER_OPERATORS: Operator[] = [
+ isOperator,
+ isNotOperator,
+ isOneOfOperator,
+ isNotOneOfOperator,
+ isBetweenOperator,
+ isNotBetweenOperator,
+ existsOperator,
+ doesNotExistOperator,
+];
diff --git a/src/fixtures/filters/exists_filter.js b/src/ui/public/filter_bar/filter_editor/lib/fixtures/exists_filter.ts
similarity index 73%
rename from src/fixtures/filters/exists_filter.js
rename to src/ui/public/filter_bar/filter_editor/lib/fixtures/exists_filter.ts
index 0c4d6138a99b7..a17f767006f3e 100644
--- a/src/fixtures/filters/exists_filter.js
+++ b/src/ui/public/filter_bar/filter_editor/lib/fixtures/exists_filter.ts
@@ -17,19 +17,18 @@
* under the License.
*/
-export const existsFilter = {
- 'meta': {
- 'index': 'logstash-*',
- 'negate': false,
- 'disabled': false,
- 'type': 'exists',
- 'key': 'machine.os',
- 'value': 'exists'
+import { ExistsFilter, FilterStateStore } from '@kbn/es-query';
+
+export const existsFilter: ExistsFilter = {
+ meta: {
+ index: 'logstash-*',
+ negate: false,
+ disabled: false,
+ type: 'exists',
+ key: 'machine.os',
+ alias: null,
},
- 'exists': {
- 'field': 'machine.os'
+ $state: {
+ store: FilterStateStore.APP_STATE,
},
- '$state': {
- 'store': 'appState'
- }
};
diff --git a/src/fixtures/filters/phrase_filter.js b/src/ui/public/filter_bar/filter_editor/lib/fixtures/phrase_filter.ts
similarity index 80%
rename from src/fixtures/filters/phrase_filter.js
rename to src/ui/public/filter_bar/filter_editor/lib/fixtures/phrase_filter.ts
index 22f02347d94aa..77bb8e06c801a 100644
--- a/src/fixtures/filters/phrase_filter.js
+++ b/src/ui/public/filter_bar/filter_editor/lib/fixtures/phrase_filter.ts
@@ -17,24 +17,22 @@
* under the License.
*/
-export const phraseFilter = {
+import { FilterStateStore, PhraseFilter } from '@kbn/es-query';
+
+export const phraseFilter: PhraseFilter = {
meta: {
negate: false,
index: 'logstash-*',
type: 'phrase',
key: 'machine.os',
value: 'ios',
- disabled: false
- },
- query: {
- match: {
- 'machine.os': {
- query: 'ios',
- type: 'phrase'
- }
- }
+ disabled: false,
+ alias: null,
+ params: {
+ query: 'ios',
+ },
},
$state: {
- store: 'appState'
- }
+ store: FilterStateStore.APP_STATE,
+ },
};
diff --git a/src/fixtures/filters/phrases_filter.js b/src/ui/public/filter_bar/filter_editor/lib/fixtures/phrases_filter.ts
similarity index 70%
rename from src/fixtures/filters/phrases_filter.js
rename to src/ui/public/filter_bar/filter_editor/lib/fixtures/phrases_filter.ts
index 184f1268c9da0..e86c3ee1318e3 100644
--- a/src/fixtures/filters/phrases_filter.js
+++ b/src/ui/public/filter_bar/filter_editor/lib/fixtures/phrases_filter.ts
@@ -17,37 +17,20 @@
* under the License.
*/
-export const phrasesFilter = {
+import { FilterStateStore, PhrasesFilter } from '@kbn/es-query';
+
+export const phrasesFilter: PhrasesFilter = {
meta: {
index: 'logstash-*',
type: 'phrases',
key: 'machine.os.raw',
value: 'win xp, osx',
- params: [
- 'win xp',
- 'osx'
- ],
+ params: ['win xp', 'osx'],
negate: false,
- disabled: false
- },
- query: {
- bool: {
- should: [
- {
- match_phrase: {
- 'machine.os.raw': 'win xp'
- }
- },
- {
- match_phrase: {
- 'machine.os.raw': 'osx'
- }
- }
- ],
- minimum_should_match: 1
- }
+ disabled: false,
+ alias: null,
},
$state: {
- store: 'appState'
- }
+ store: FilterStateStore.APP_STATE,
+ },
};
diff --git a/src/ui/public/filter_bar/filter_editor/lib/fixtures/range_filter.ts b/src/ui/public/filter_bar/filter_editor/lib/fixtures/range_filter.ts
new file mode 100644
index 0000000000000..f6daf9cb36f11
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/lib/fixtures/range_filter.ts
@@ -0,0 +1,39 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { FilterStateStore, RangeFilter } from '@kbn/es-query';
+
+export const rangeFilter: RangeFilter = {
+ meta: {
+ index: 'logstash-*',
+ negate: false,
+ disabled: false,
+ alias: null,
+ type: 'range',
+ key: 'bytes',
+ value: '0 to 10',
+ params: {
+ gte: 0,
+ lt: 10,
+ },
+ },
+ $state: {
+ store: FilterStateStore.APP_STATE,
+ },
+};
diff --git a/src/ui/public/filter_bar/filter_editor/phrase_suggestor.tsx b/src/ui/public/filter_bar/filter_editor/phrase_suggestor.tsx
new file mode 100644
index 0000000000000..75073def34281
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/phrase_suggestor.tsx
@@ -0,0 +1,73 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Component } from 'react';
+import chrome from 'ui/chrome';
+import { Field, IndexPattern } from 'ui/index_patterns';
+import { getSuggestions } from 'ui/value_suggestions';
+const config = chrome.getUiSettingsClient();
+
+export interface PhraseSuggestorProps {
+ indexPattern: IndexPattern;
+ field?: Field;
+}
+
+export interface PhraseSuggestorState {
+ suggestions: string[];
+ isLoading: boolean;
+}
+
+/**
+ * Since both "phrase" and "phrases" filter inputs suggest values (if enabled and the field is
+ * aggregatable), we pull out the common logic for requesting suggestions into this component
+ * which both of them extend.
+ */
+export class PhraseSuggestor extends Component<
+ T,
+ PhraseSuggestorState
+> {
+ public state: PhraseSuggestorState = {
+ suggestions: [],
+ isLoading: false,
+ };
+
+ public componentDidMount() {
+ this.updateSuggestions();
+ }
+
+ protected isSuggestingValues() {
+ const shouldSuggestValues = config.get('filterEditor:suggestValues');
+ const { field } = this.props;
+ return shouldSuggestValues && field && field.aggregatable && field.type === 'string';
+ }
+
+ protected onSearchChange = (value: string | number | boolean) => {
+ this.updateSuggestions(`${value}`);
+ };
+
+ protected async updateSuggestions(value: string = '') {
+ const { indexPattern, field } = this.props as PhraseSuggestorProps;
+ if (!field || !this.isSuggestingValues()) {
+ return;
+ }
+ this.setState({ isLoading: true });
+ const suggestions = await getSuggestions(indexPattern.title, field, value);
+ this.setState({ suggestions, isLoading: false });
+ }
+}
diff --git a/src/ui/public/filter_bar/filter_editor/phrase_value_input.tsx b/src/ui/public/filter_bar/filter_editor/phrase_value_input.tsx
new file mode 100644
index 0000000000000..06b826d681d0c
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/phrase_value_input.tsx
@@ -0,0 +1,88 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EuiFormRow } from '@elastic/eui';
+import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
+import { uniq } from 'lodash';
+import React from 'react';
+import { GenericComboBox, GenericComboBoxProps } from './generic_combo_box';
+import { PhraseSuggestor, PhraseSuggestorProps } from './phrase_suggestor';
+import { ValueInputType } from './value_input_type';
+
+interface Props extends PhraseSuggestorProps {
+ value?: string;
+ onChange: (value: string | number | boolean) => void;
+ intl: InjectedIntl;
+}
+
+class PhraseValueInputUI extends PhraseSuggestor {
+ public render() {
+ return (
+
+ {this.isSuggestingValues() ? (
+ this.renderWithSuggestions()
+ ) : (
+
+ )}
+
+ );
+ }
+
+ private renderWithSuggestions() {
+ const { suggestions } = this.state;
+ const { value, intl, onChange } = this.props;
+ const options = value ? uniq([value, ...suggestions]) : suggestions;
+ return (
+ option}
+ selectedOptions={value ? [value] : []}
+ onChange={([newValue = '']) => onChange(newValue)}
+ onSearchChange={this.onSearchChange}
+ singleSelection={{ asPlainText: true }}
+ onCreateOption={onChange}
+ isClearable={false}
+ data-test-subj="filterParamsComboBox phraseParamsComboxBox"
+ />
+ );
+ }
+}
+
+function StringComboBox(props: GenericComboBoxProps) {
+ return GenericComboBox(props);
+}
+
+export const PhraseValueInput = injectI18n(PhraseValueInputUI);
diff --git a/src/ui/public/filter_bar/filter_editor/phrases_values_input.tsx b/src/ui/public/filter_bar/filter_editor/phrases_values_input.tsx
new file mode 100644
index 0000000000000..bef1433223e74
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/phrases_values_input.tsx
@@ -0,0 +1,67 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EuiFormRow } from '@elastic/eui';
+import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
+import { uniq } from 'lodash';
+import React from 'react';
+import { GenericComboBox, GenericComboBoxProps } from './generic_combo_box';
+import { PhraseSuggestor, PhraseSuggestorProps } from './phrase_suggestor';
+
+interface Props extends PhraseSuggestorProps {
+ values?: string[];
+ onChange: (values: string[]) => void;
+ intl: InjectedIntl;
+}
+
+class PhrasesValuesInputUI extends PhraseSuggestor {
+ public render() {
+ const { suggestions } = this.state;
+ const { values, intl, onChange } = this.props;
+ const options = values ? uniq([...values, ...suggestions]) : suggestions;
+ return (
+
+ option}
+ selectedOptions={values || []}
+ onCreateOption={(option: string) => onChange([...(values || []), option])}
+ onChange={onChange}
+ isClearable={false}
+ data-test-subj="filterParamsComboBox phrasesParamsComboxBox"
+ />
+
+ );
+ }
+}
+
+function StringComboBox(props: GenericComboBoxProps) {
+ return GenericComboBox(props);
+}
+
+export const PhrasesValuesInput = injectI18n(PhrasesValuesInputUI);
diff --git a/src/ui/public/filter_bar/filter_editor/range_value_input.tsx b/src/ui/public/filter_bar/filter_editor/range_value_input.tsx
new file mode 100644
index 0000000000000..7343c5722f226
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/range_value_input.tsx
@@ -0,0 +1,121 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIcon, EuiLink } from '@elastic/eui';
+import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
+import { get } from 'lodash';
+import { Component } from 'react';
+import React from 'react';
+import { getDocLink } from 'ui/documentation_links';
+import { Field } from 'ui/index_patterns';
+import { ValueInputType } from './value_input_type';
+
+interface RangeParams {
+ from: number | string;
+ to: number | string;
+}
+
+type RangeParamsPartial = Partial;
+
+interface Props {
+ field?: Field;
+ value?: RangeParams;
+ onChange: (params: RangeParamsPartial) => void;
+ intl: InjectedIntl;
+}
+
+class RangeValueInputUI extends Component {
+ public constructor(props: Props) {
+ super(props);
+ }
+
+ public render() {
+ const type = this.props.field ? this.props.field.type : 'string';
+
+ return (
+ {
+ public render() {
+ const value = this.props.value;
+ let inputElement: React.ReactNode;
+ switch (this.props.type) {
+ case 'string':
+ inputElement = (
+
+ );
+ break;
+ case 'number':
+ inputElement = (
+
+ );
+ break;
+ case 'date':
+ inputElement = (
+
+ );
+ break;
+ case 'ip':
+ inputElement = (
+
+ );
+ break;
+ case 'boolean':
+ inputElement = (
+
+ );
+ break;
+ default:
+ break;
+ }
+
+ return inputElement;
+ }
+
+ private onBoolChange = (event: React.ChangeEvent) => {
+ const boolValue = event.target.value === 'true';
+ this.props.onChange(boolValue);
+ };
+
+ private onChange = (event: React.ChangeEvent) => {
+ const params = event.target.value;
+ this.props.onChange(params);
+ };
+}
+
+export const ValueInputType = injectI18n(ValueInputTypeUI);
diff --git a/src/ui/public/filter_bar/filter_item.tsx b/src/ui/public/filter_bar/filter_item.tsx
new file mode 100644
index 0000000000000..82ed8c692d549
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_item.tsx
@@ -0,0 +1,226 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EuiContextMenu, EuiPopover } from '@elastic/eui';
+import {
+ Filter,
+ isFilterPinned,
+ toggleFilterDisabled,
+ toggleFilterNegated,
+ toggleFilterPinned,
+} from '@kbn/es-query';
+import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
+import classNames from 'classnames';
+import React, { Component } from 'react';
+import { IndexPattern } from 'ui/index_patterns';
+import { FilterEditor } from './filter_editor';
+import { FilterView } from './filter_view';
+
+interface Props {
+ id: string;
+ filter: Filter;
+ indexPatterns: IndexPattern[];
+ className?: string;
+ onUpdate: (filter: Filter) => void;
+ onRemove: () => void;
+ intl: InjectedIntl;
+}
+
+interface State {
+ isPopoverOpen: boolean;
+}
+
+class FilterItemUI extends Component {
+ public state = {
+ isPopoverOpen: false,
+ };
+
+ public render() {
+ const { filter, id } = this.props;
+ const { negate, disabled } = filter.meta;
+
+ const classes = classNames(
+ 'globalFilterItem',
+ {
+ 'globalFilterItem-isDisabled': disabled,
+ 'globalFilterItem-isPinned': isFilterPinned(filter),
+ 'globalFilterItem-isExcluded': negate,
+ },
+ this.props.className
+ );
+
+ const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : '';
+ const dataTestSubjValue = filter.meta.value ? `filter-value-${filter.meta.value}` : '';
+ const dataTestSubjDisabled = `filter-${
+ this.props.filter.meta.disabled ? 'disabled' : 'enabled'
+ }`;
+
+ const badge = (
+ this.props.onRemove()}
+ onClick={this.togglePopover}
+ data-test-subj={`filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue}`}
+ />
+ );
+
+ const panelTree = [
+ {
+ id: 0,
+ items: [
+ {
+ name: isFilterPinned(filter)
+ ? this.props.intl.formatMessage({
+ id: 'common.ui.filterBar.unpinFilterButtonLabel',
+ defaultMessage: 'Unpin',
+ })
+ : this.props.intl.formatMessage({
+ id: 'common.ui.filterBar.pinFilterButtonLabel',
+ defaultMessage: 'Pin across all apps',
+ }),
+ icon: 'pin',
+ onClick: () => {
+ this.closePopover();
+ this.onTogglePinned();
+ },
+ 'data-test-subj': 'pinFilter',
+ },
+ {
+ name: this.props.intl.formatMessage({
+ id: 'common.ui.filterBar.editFilterButtonLabel',
+ defaultMessage: 'Edit filter',
+ }),
+ icon: 'pencil',
+ panel: 1,
+ 'data-test-subj': 'editFilter',
+ },
+ {
+ name: negate
+ ? this.props.intl.formatMessage({
+ id: 'common.ui.filterBar.includeFilterButtonLabel',
+ defaultMessage: 'Include results',
+ })
+ : this.props.intl.formatMessage({
+ id: 'common.ui.filterBar.excludeFilterButtonLabel',
+ defaultMessage: 'Exclude results',
+ }),
+ icon: negate ? 'plusInCircle' : 'minusInCircle',
+ onClick: () => {
+ this.closePopover();
+ this.onToggleNegated();
+ },
+ 'data-test-subj': 'negateFilter',
+ },
+ {
+ name: disabled
+ ? this.props.intl.formatMessage({
+ id: 'common.ui.filterBar.enableFilterButtonLabel',
+ defaultMessage: 'Re-enable',
+ })
+ : this.props.intl.formatMessage({
+ id: 'common.ui.filterBar.disableFilterButtonLabel',
+ defaultMessage: 'Temporarily disable',
+ }),
+ icon: `${disabled ? 'eye' : 'eyeClosed'}`,
+ onClick: () => {
+ this.closePopover();
+ this.onToggleDisabled();
+ },
+ 'data-test-subj': 'disableFilter',
+ },
+ {
+ name: this.props.intl.formatMessage({
+ id: 'common.ui.filterBar.deleteFilterButtonLabel',
+ defaultMessage: 'Delete',
+ }),
+ icon: 'trash',
+ onClick: () => {
+ this.closePopover();
+ this.props.onRemove();
+ },
+ 'data-test-subj': 'deleteFilter',
+ },
+ ],
+ },
+ {
+ id: 1,
+ width: 400,
+ content: (
+
+
+
+ );
+ }
+
+ private closePopover = () => {
+ this.setState({
+ isPopoverOpen: false,
+ });
+ };
+
+ private togglePopover = () => {
+ this.setState({
+ isPopoverOpen: !this.state.isPopoverOpen,
+ });
+ };
+
+ private onSubmit = (filter: Filter) => {
+ this.closePopover();
+ this.props.onUpdate(filter);
+ };
+
+ private onTogglePinned = () => {
+ const filter = toggleFilterPinned(this.props.filter);
+ this.props.onUpdate(filter);
+ };
+
+ private onToggleNegated = () => {
+ const filter = toggleFilterNegated(this.props.filter);
+ this.props.onUpdate(filter);
+ };
+
+ private onToggleDisabled = () => {
+ const filter = toggleFilterDisabled(this.props.filter);
+ this.props.onUpdate(filter);
+ };
+}
+
+export const FilterItem = injectI18n(FilterItemUI);
diff --git a/src/ui/public/filter_bar/filter_pill/filter_pill.html b/src/ui/public/filter_bar/filter_pill/filter_pill.html
deleted file mode 100644
index f79d42665e8fc..0000000000000
--- a/src/ui/public/filter_bar/filter_pill/filter_pill.html
+++ /dev/null
@@ -1,97 +0,0 @@
- = ({ filter, ...rest }: Props) => {
+ let title = `Filter: ${getFilterDisplayText(filter)}. ${i18n.translate(
+ 'common.ui.filterBar.moreFilterActionsMessage',
+ {
+ defaultMessage: 'Select for more filter actions.',
+ }
+ )}`;
+
+ if (isFilterPinned(filter)) {
+ title = `${i18n.translate('common.ui.filterBar.pinnedFilterPrefix', {
+ defaultMessage: 'Pinned',
+ })} ${title}`;
+ }
+ if (filter.meta.disabled) {
+ title = `${i18n.translate('common.ui.filterBar.disabledFilterPrefix', {
+ defaultMessage: 'Disabled',
+ })} ${title}`;
+ }
+
+ return (
+
+ {getFilterDisplayText(filter)}
+
+ );
+};
+
+export function getFilterDisplayText(filter: Filter) {
+ if (filter.meta.alias !== null) {
+ return filter.meta.alias;
+ }
+
+ const prefix = filter.meta.negate
+ ? ` ${i18n.translate('common.ui.filterBar.negatedFilterPrefix', {
+ defaultMessage: 'NOT ',
+ })}`
+ : '';
+
+ switch (filter.meta.type) {
+ case 'exists':
+ return `${prefix}${filter.meta.key} ${existsOperator.message}`;
+ case 'geo_bounding_box':
+ return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
+ case 'geo_polygon':
+ return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
+ case 'phrase':
+ return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
+ case 'phrases':
+ return `${prefix}${filter.meta.key} ${isOneOfOperator.message} ${filter.meta.value}`;
+ case 'query_string':
+ return `${prefix}${filter.meta.value}`;
+ case 'range':
+ return `${prefix}${filter.meta.key}: ${filter.meta.value}`;
+ default:
+ return `${prefix}${JSON.stringify(filter.query)}`;
+ }
+}
diff --git a/src/ui/public/filter_bar/index.ts b/src/ui/public/filter_bar/index.ts
new file mode 100644
index 0000000000000..cdf49a72e9554
--- /dev/null
+++ b/src/ui/public/filter_bar/index.ts
@@ -0,0 +1,22 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import './directive';
+
+export { FilterBar } from './filter_bar';
diff --git a/src/ui/public/filter_bar/lib/__tests__/disable_filter.js b/src/ui/public/filter_bar/lib/__tests__/disable_filter.js
deleted file mode 100644
index 2541ec6cacae0..0000000000000
--- a/src/ui/public/filter_bar/lib/__tests__/disable_filter.js
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-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);
- });
-});
diff --git a/src/ui/public/filter_bar/lib/__tests__/filter_applied_and_unwrap.js b/src/ui/public/filter_bar/lib/__tests__/filter_applied_and_unwrap.js
deleted file mode 100644
index 819035452837e..0000000000000
--- a/src/ui/public/filter_bar/lib/__tests__/filter_applied_and_unwrap.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from 'expect.js';
-import { filterAppliedAndUnwrap } from '../filter_applied_and_unwrap';
-
-describe('Filter Bar Directive', function () {
- describe('filterAppliedAndUnwrap()', function () {
-
- const filters = [
- { meta: { apply: true }, exists: { field: '_type' } },
- { meta: { apply: false }, query: { query_string: { query: 'foo:bar' } } }
- ];
-
- it('should filter the applied and unwrap the filter', function () {
- const results = filterAppliedAndUnwrap(filters);
- expect(results).to.have.length(1);
- expect(results[0]).to.eql(filters[0]);
- });
-
- });
-});
diff --git a/src/ui/public/filter_bar/lib/__tests__/filter_out_time_based_filter.js b/src/ui/public/filter_bar/lib/__tests__/filter_out_time_based_filter.js
deleted file mode 100644
index 009683e3c07f4..0000000000000
--- a/src/ui/public/filter_bar/lib/__tests__/filter_out_time_based_filter.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from 'expect.js';
-import ngMock from 'ng_mock';
-import { FilterBarLibFilterOutTimeBasedFilterProvider } from '../filter_out_time_based_filter';
-
-describe('Filter Bar Directive', function () {
- describe('filterOutTimeBasedFilter()', function () {
-
- let filterOutTimeBasedFilter;
- let $rootScope;
-
- beforeEach(ngMock.module(
- 'kibana',
- 'kibana/courier',
- function ($provide) {
- $provide.service('indexPatterns', require('fixtures/mock_index_patterns'));
- }
- ));
-
- beforeEach(ngMock.inject(function (Private, _$rootScope_) {
- filterOutTimeBasedFilter = Private(FilterBarLibFilterOutTimeBasedFilterProvider);
- $rootScope = _$rootScope_;
- }));
-
- it('should return the matching filter for the default time field', function (done) {
- const filters = [
- { meta: { index: 'logstash-*' }, query: { match: { _type: { query: 'apache', type: 'phrase' } } } },
- { meta: { index: 'logstash-*' }, range: { 'time': { gt: 1388559600000, lt: 1388646000000 } } }
- ];
- filterOutTimeBasedFilter(filters).then(function (results) {
- expect(results).to.have.length(1);
- expect(results).to.not.contain(filters[1]);
- done();
- });
- $rootScope.$apply();
- });
-
- });
-});
diff --git a/src/ui/public/filter_bar/lib/filter_out_time_based_filter.js b/src/ui/public/filter_bar/lib/filter_out_time_based_filter.js
deleted file mode 100644
index 0aa628e6ad069..0000000000000
--- a/src/ui/public/filter_bar/lib/filter_out_time_based_filter.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-
-export function FilterBarLibFilterOutTimeBasedFilterProvider(indexPatterns, Promise) {
- return Promise.method(function (filters) {
- const id = _.get(filters, '[0].meta.index');
- if (id == null) return;
-
- return indexPatterns.get(id).then(function (indexPattern) {
- return _.filter(filters, function (filter) {
- return !(filter.range && filter.range[indexPattern.timeFieldName]);
- });
- });
- });
-}
diff --git a/src/ui/public/filter_bar/lib/map_phrase.js b/src/ui/public/filter_bar/lib/map_phrase.js
index 25018d4746e2b..687c844f7eec7 100644
--- a/src/ui/public/filter_bar/lib/map_phrase.js
+++ b/src/ui/public/filter_bar/lib/map_phrase.js
@@ -30,8 +30,8 @@ export function FilterBarLibMapPhraseProvider(Promise, indexPatterns) {
function getParams(indexPattern) {
const type = 'phrase';
const key = isScriptedPhraseFilter ? filter.meta.field : Object.keys(filter.query.match)[0];
- const params = isScriptedPhraseFilter ? filter.script.script.params : filter.query.match[key];
- const query = isScriptedPhraseFilter ? params.value : params.query;
+ const query = isScriptedPhraseFilter ? filter.script.script.params.value : filter.query.match[key].query;
+ const params = { query };
// Sometimes a filter will end up with an invalid index or field param. This could happen for a lot of reasons,
// for example a user might manually edit the url or the index pattern's ID might change due to
@@ -54,6 +54,6 @@ export function FilterBarLibMapPhraseProvider(Promise, indexPatterns) {
}
function isScriptedPhrase(filter) {
- const params = _.get(filter, ['script', 'script', 'params']);
- return params && params.value;
+ const value = _.get(filter, ['script', 'script', 'params', 'value']);
+ return typeof value !== 'undefined';
}
diff --git a/src/ui/public/filter_bar/query_filter.js b/src/ui/public/filter_bar/query_filter.js
index 0c1e68c084535..5b73013973fb8 100644
--- a/src/ui/public/filter_bar/query_filter.js
+++ b/src/ui/public/filter_bar/query_filter.js
@@ -24,10 +24,13 @@ import { uniqFilters } from './lib/uniq_filters';
import { compareFilters } from './lib/compare_filters';
import { EventsProvider } from '../events';
import { FilterBarLibMapAndFlattenFiltersProvider } from './lib/map_and_flatten_filters';
+import { FilterBarLibExtractTimeFilterProvider } from './lib/extract_time_filter';
+import { changeTimeFilter } from './lib/change_time_filter';
export function FilterBarQueryFilterProvider(Private, $rootScope, getAppState, globalState, config) {
const EventEmitter = Private(EventsProvider);
const mapAndFlattenFilters = Private(FilterBarLibMapAndFlattenFiltersProvider);
+ const extractTimeFilter = Private(FilterBarLibExtractTimeFilterProvider);
const queryFilter = new EventEmitter();
@@ -216,6 +219,24 @@ export function FilterBarQueryFilterProvider(Private, $rootScope, getAppState, g
executeOnFilters(pin);
};
+ queryFilter.setFilters = filters => {
+ return mapAndFlattenFilters(filters)
+ .then(mappedFilters => {
+ const appState = getAppState();
+ const [globalFilters, appFilters] = _.partition(mappedFilters, filter => {
+ return filter.$state.store === 'globalState';
+ });
+ globalState.filters = globalFilters;
+ if (appState) appState.filters = appFilters;
+ });
+ };
+
+ queryFilter.addFiltersAndChangeTimeFilter = async filters => {
+ const timeFilter = await extractTimeFilter(filters);
+ if (timeFilter) changeTimeFilter(timeFilter);
+ queryFilter.addFilters(filters.filter(filter => filter !== timeFilter));
+ };
+
initWatchers();
return queryFilter;
@@ -268,7 +289,6 @@ export function FilterBarQueryFilterProvider(Private, $rootScope, getAppState, g
// ensure we don't mutate the filters passed in
const globalFilters = gFilters ? _.cloneDeep(gFilters) : [];
const appFilters = aFilters ? _.cloneDeep(aFilters) : [];
- compareOptions = _.defaults(compareOptions || {}, { disabled: true });
// existing globalFilters should be mutated by appFilters
_.each(appFilters, function (filter, i) {
@@ -293,8 +313,8 @@ export function FilterBarQueryFilterProvider(Private, $rootScope, getAppState, g
return [
// Reverse filters after uniq again, so they are still in the order, they
// were before updating them
- uniqFilters(globalFilters, { disabled: true }).reverse(),
- uniqFilters(appFilters, { disabled: true }).reverse()
+ uniqFilters(globalFilters).reverse(),
+ uniqFilters(appFilters).reverse()
];
}
@@ -332,8 +352,7 @@ export function FilterBarQueryFilterProvider(Private, $rootScope, getAppState, g
// reconcile filter in global and app states
const filters = mergeStateFilters(next[0], next[1]);
- const globalFilters = filters[0];
- const appFilters = filters[1];
+ const [globalFilters, appFilters] = filters;
const appState = getAppState();
// save the state, as it may have updated
diff --git a/src/ui/public/filter_editor/filter_editor.html b/src/ui/public/filter_editor/filter_editor.html
deleted file mode 100644
index 0b69a1d5661d8..0000000000000
--- a/src/ui/public/filter_editor/filter_editor.html
+++ /dev/null
@@ -1,148 +0,0 @@
-
-
-
- {{$select.selected.name}}
-
-
-
-
-
-
diff --git a/src/ui/public/filter_editor/filter_field_select.js b/src/ui/public/filter_editor/filter_field_select.js
deleted file mode 100644
index f1aeb276d58d1..0000000000000
--- a/src/ui/public/filter_editor/filter_field_select.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import 'angular-ui-select';
-import { uiModules } from '../modules';
-import { getFilterableFields } from './lib/filter_editor_utils';
-import template from './filter_field_select.html';
-import '../directives/ui_select_focus_on';
-import '../directives/scroll_bottom';
-import '../filters/sort_prefix_first';
-
-const module = uiModules.get('kibana');
-module.directive('filterFieldSelect', function () {
- return {
- restrict: 'E',
- template,
- scope: {
- indexPatterns: '=',
- field: '=',
- onSelect: '&'
- },
- link: function ($scope) {
- $scope.$watch('indexPatterns', (indexPatterns) => {
- $scope.fieldOptions = getFilterableFields(indexPatterns);
- });
-
- $scope.getFieldIndexPattern = (field) => {
- return field.indexPattern.title;
- };
-
- $scope.increaseLimit = () => $scope.limit += 50;
- $scope.resetLimit = () => $scope.limit = 50;
- $scope.resetLimit();
- }
- };
-});
diff --git a/src/ui/public/filter_editor/filter_operator_select.html b/src/ui/public/filter_editor/filter_operator_select.html
deleted file mode 100644
index 78b730b93b58a..0000000000000
--- a/src/ui/public/filter_editor/filter_operator_select.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
- {{$select.selected.name}}
-
-
-
-
-
diff --git a/src/ui/public/filter_editor/filter_operator_select.js b/src/ui/public/filter_editor/filter_operator_select.js
deleted file mode 100644
index 3d877aee869b7..0000000000000
--- a/src/ui/public/filter_editor/filter_operator_select.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import 'angular-ui-select';
-import { uiModules } from '../modules';
-import { getOperatorOptions } from './lib/filter_editor_utils';
-import template from './filter_operator_select.html';
-import '../directives/ui_select_focus_on';
-
-const module = uiModules.get('kibana');
-module.directive('filterOperatorSelect', function () {
- return {
- restrict: 'E',
- template,
- scope: {
- field: '=',
- operator: '=',
- onSelect: '&'
- },
- link: function ($scope) {
- $scope.$watch('field', (field) => {
- $scope.operatorOptions = getOperatorOptions(field);
- });
- }
- };
-});
diff --git a/src/ui/public/filter_editor/filter_query_dsl_editor.html b/src/ui/public/filter_editor/filter_query_dsl_editor.html
deleted file mode 100644
index 999dae7c1df0c..0000000000000
--- a/src/ui/public/filter_editor/filter_query_dsl_editor.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
diff --git a/src/ui/public/filter_editor/filter_query_dsl_editor.js b/src/ui/public/filter_editor/filter_query_dsl_editor.js
deleted file mode 100644
index 16f763378ae05..0000000000000
--- a/src/ui/public/filter_editor/filter_query_dsl_editor.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import 'ace';
-import _ from 'lodash';
-import { uiModules } from '../modules';
-import template from './filter_query_dsl_editor.html';
-import '../accessibility/kbn_ui_ace_keyboard_mode';
-
-const module = uiModules.get('kibana');
-module.directive('filterQueryDslEditor', function () {
- return {
- restrict: 'E',
- template,
- scope: {
- isVisible: '=',
- filter: '=',
- onChange: '&'
- },
- link: {
- pre: function ($scope) {
- let aceEditor;
-
- $scope.queryDsl = _.omit($scope.filter, ['meta', '$state']);
- $scope.aceLoaded = function (editor) {
- aceEditor = editor;
- editor.$blockScrolling = Infinity;
- const session = editor.getSession();
- session.setTabSize(2);
- session.setUseSoftTabs(true);
- };
-
- $scope.$watch('isVisible', isVisible => {
- // Tell the editor to re-render itself now that it's visible, otherwise it won't
- // show up in the UI.
- if (isVisible && aceEditor) {
- aceEditor.renderer.updateFull();
- }
- });
- }
- }
- };
-});
diff --git a/src/ui/public/filter_editor/lib/__tests__/filter_editor_utils.js b/src/ui/public/filter_editor/lib/__tests__/filter_editor_utils.js
deleted file mode 100644
index 61aadc4c07487..0000000000000
--- a/src/ui/public/filter_editor/lib/__tests__/filter_editor_utils.js
+++ /dev/null
@@ -1,396 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from 'expect.js';
-import ngMock from 'ng_mock';
-import sinon from 'sinon';
-import Promise from 'bluebird';
-import {
- phraseFilter,
- scriptedPhraseFilter,
- phrasesFilter,
- rangeFilter,
- existsFilter
-} from 'fixtures/filters';
-import stubbedLogstashIndexPattern from 'fixtures/stubbed_logstash_index_pattern';
-import stubbedLogstashFields from 'fixtures/logstash_fields';
-import { FILTER_OPERATORS } from '../filter_operators';
-import {
- getQueryDslFromFilter,
- getFieldFromFilter,
- getOperatorFromFilter,
- getParamsFromFilter,
- getFilterableFields,
- getOperatorOptions,
- isFilterValid,
- buildFilter,
- areIndexPatternsProvided,
- isFilterPinned
-} from '../filter_editor_utils';
-
-describe('FilterEditorUtils', function () {
- beforeEach(ngMock.module('kibana'));
-
- let indexPattern;
- let fields;
- beforeEach(function () {
- ngMock.inject(function (Private) {
- indexPattern = Private(stubbedLogstashIndexPattern);
- fields = stubbedLogstashFields();
- });
- });
-
- describe('getQueryDslFromFilter', function () {
- it('should return query DSL without meta and $state', function () {
- const queryDsl = getQueryDslFromFilter(phraseFilter);
- expect(queryDsl).to.not.have.key('meta');
- expect(queryDsl).to.not.have.key('$state');
- expect(queryDsl).to.have.key('query');
- });
- });
-
- describe('getFieldFromFilter', function () {
- let indexPatterns;
- beforeEach(function () {
- indexPatterns = {
- get: sinon.stub().returns(Promise.resolve(indexPattern))
- };
- });
-
- it('should return the field from the filter', function (done) {
- getFieldFromFilter(phraseFilter, indexPatterns)
- .then((field) => {
- expect(field).to.be.ok();
- done();
- });
- });
- });
-
- describe('getOperatorFromFilter', function () {
- it('should return "is" for phrase filter', function () {
- const operator = getOperatorFromFilter(phraseFilter);
- expect(operator.name).to.be('is');
- expect(operator.negate).to.be(false);
- });
-
- it('should return "is not" for negated phrase filter', function () {
- const negate = phraseFilter.meta.negate;
- phraseFilter.meta.negate = true;
- const operator = getOperatorFromFilter(phraseFilter);
- expect(operator.name).to.be('is not');
- expect(operator.negate).to.be(true);
- phraseFilter.meta.negate = negate;
- });
-
- it('should return "is one of" for phrases filter', function () {
- const operator = getOperatorFromFilter(phrasesFilter);
- expect(operator.name).to.be('is one of');
- expect(operator.negate).to.be(false);
- });
-
- it('should return "is not one of" for negated phrases filter', function () {
- const negate = phrasesFilter.meta.negate;
- phrasesFilter.meta.negate = true;
- const operator = getOperatorFromFilter(phrasesFilter);
- expect(operator.name).to.be('is not one of');
- expect(operator.negate).to.be(true);
- phrasesFilter.meta.negate = negate;
- });
-
- it('should return "is between" for range filter', function () {
- const operator = getOperatorFromFilter(rangeFilter);
- expect(operator.name).to.be('is between');
- expect(operator.negate).to.be(false);
- });
-
- it('should return "is not between" for negated range filter', function () {
- const negate = rangeFilter.meta.negate;
- rangeFilter.meta.negate = true;
- const operator = getOperatorFromFilter(rangeFilter);
- expect(operator.name).to.be('is not between');
- expect(operator.negate).to.be(true);
- rangeFilter.meta.negate = negate;
- });
-
- it('should return "exists" for exists filter', function () {
- const operator = getOperatorFromFilter(existsFilter);
- expect(operator.name).to.be('exists');
- expect(operator.negate).to.be(false);
- });
-
- it('should return "does not exists" for negated exists filter', function () {
- const negate = existsFilter.meta.negate;
- existsFilter.meta.negate = true;
- const operator = getOperatorFromFilter(existsFilter);
- expect(operator.name).to.be('does not exist');
- expect(operator.negate).to.be(true);
- existsFilter.meta.negate = negate;
- });
- });
-
- describe('getParamsFromFilter', function () {
- it('should retrieve params from phrase filter', function () {
- const params = getParamsFromFilter(phraseFilter);
- expect(params.phrase).to.be('ios');
- });
-
- it('should retrieve params from scripted phrase filter', function () {
- const params = getParamsFromFilter(scriptedPhraseFilter);
- expect(params.phrase).to.be('i am a string');
- });
-
- it('should retrieve params from phrases filter', function () {
- const params = getParamsFromFilter(phrasesFilter);
- expect(params.phrases).to.eql(['win xp', 'osx']);
- });
-
- it('should retrieve params from range filter', function () {
- const params = getParamsFromFilter(rangeFilter);
- expect(params.range).to.eql({ from: 0, to: 10 });
- });
-
- it('should return undefined for exists filter', function () {
- const params = getParamsFromFilter(existsFilter);
- expect(params.exists).to.not.be.ok();
- });
- });
-
- describe('getFilterableFields', function () {
- it('returns an empty array when no index patterns are provided', function () {
- const fieldOptions = getFilterableFields();
- expect(fieldOptions).to.eql([]);
- });
-
- it('returns the list of fields from the given index patterns', function () {
- const fieldOptions = getFilterableFields([indexPattern]);
- expect(fieldOptions).to.be.an('array');
- expect(fieldOptions.length).to.be.greaterThan(0);
- });
-
- it('limits the fields to the filterable fields', function () {
- const fieldOptions = getFilterableFields([indexPattern]);
- const nonFilterableFields = fieldOptions.filter(field => !field.filterable);
- expect(nonFilterableFields.length).to.be(0);
- });
- });
-
- describe('getOperatorOptions', function () {
- it('returns range for number fields', function () {
- const field = fields.find(field => field.type === 'number');
- const operatorOptions = getOperatorOptions(field);
- const rangeOperator = operatorOptions.find(operator => operator.type === 'range');
- expect(rangeOperator).to.be.ok();
- });
-
- it('does not return range for string fields', function () {
- const field = fields.find(field => field.type === 'string');
- const operatorOptions = getOperatorOptions(field);
- const rangeOperator = operatorOptions.find(operator => operator.type === 'range');
- expect(rangeOperator).to.not.be.ok();
- });
-
- it('returns operators without field type restrictions', function () {
- const operatorOptions = getOperatorOptions();
- const operatorsWithoutFieldTypes = FILTER_OPERATORS.filter(operator => !operator.fieldTypes);
- expect(operatorOptions.length).to.be(operatorsWithoutFieldTypes.length);
- });
- });
-
- describe('isFilterValid', function () {
- it('should return false if field is not provided', function () {
- const field = null;
- const operator = FILTER_OPERATORS[0];
- const params = { phrase: 'foo' };
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.not.be.ok();
- });
-
- it('should return false if operator is not provided', function () {
- const field = fields[0];
- const operator = null;
- const params = { phrase: 'foo' };
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.not.be.ok();
- });
-
- it('should return false for phrase filter without phrase', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'phrase');
- const params = {};
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.not.be.ok();
- });
-
- it('should return true for phrase filter with phrase', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'phrase');
- const params = { phrase: 'foo' };
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.be.ok();
- });
-
- it('should return false for phrases filter without phrases', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'phrases');
- const params = {};
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.not.be.ok();
- });
-
- it('should return true for phrases filter with phrases', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'phrases');
- const params = { phrases: ['foo', 'bar'] };
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.be.ok();
- });
-
- it('should return false for range filter without range', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'range');
- const params = {};
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.not.be.ok();
- });
-
- it('should return true for range filter with from', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'range');
- const params = { range: { from: 0 } };
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.be.ok();
- });
-
- it('should return true for range filter with from/to', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'range');
- const params = { range: { from: 0, to: 10 } };
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.be.ok();
- });
-
- it('should return true for exists filter without params', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'exists');
- const params = {};
- const isValid = isFilterValid({ field, operator, params });
- expect(isValid).to.be.ok();
- });
- });
-
- describe('buildFilter', function () {
- let filterBuilder;
- beforeEach(function () {
- filterBuilder = {
- buildExistsFilter: sinon.stub().returns(existsFilter),
- buildPhraseFilter: sinon.stub().returns(phraseFilter),
- buildPhrasesFilter: sinon.stub().returns(phrasesFilter),
- buildRangeFilter: sinon.stub().returns(rangeFilter)
- };
- });
-
- it('should build phrase filters', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'phrase');
- const params = { phrase: 'foo' };
- const filter = buildFilter({ indexPattern, field, operator, params, filterBuilder });
- expect(filter).to.be.ok();
- expect(filter.meta.negate).to.be(operator.negate);
- expect(filterBuilder.buildPhraseFilter.called).to.be.ok();
- expect(filterBuilder.buildPhraseFilter.getCall(0).args[1]).to.be(params.phrase);
- });
-
- it('should build phrases filters', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'phrases');
- const params = { phrases: ['foo', 'bar'] };
- const filter = buildFilter({ indexPattern, field, operator, params, filterBuilder });
- expect(filter).to.be.ok();
- expect(filter.meta.negate).to.be(operator.negate);
- expect(filterBuilder.buildPhrasesFilter.called).to.be.ok();
- expect(filterBuilder.buildPhrasesFilter.getCall(0).args[1]).to.eql(params.phrases);
- });
-
- it('should build range filters', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'range');
- const params = { range: { from: 0, to: 10 } };
- const filter = buildFilter({ indexPattern, field, operator, params, filterBuilder });
- expect(filter).to.be.ok();
- expect(filter.meta.negate).to.be(operator.negate);
- expect(filterBuilder.buildRangeFilter.called).to.be.ok();
- const range = filterBuilder.buildRangeFilter.getCall(0).args[1];
- expect(range).to.have.property('gte');
- expect(range).to.have.property('lt');
- });
-
- it('should build exists filters', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'exists');
- const params = {};
- const filter = buildFilter({ indexPattern, field, operator, params, filterBuilder });
- expect(filter).to.be.ok();
- expect(filter.meta.negate).to.be(operator.negate);
- expect(filterBuilder.buildExistsFilter.called).to.be.ok();
- });
-
- it('should negate based on operator', function () {
- const field = fields[0];
- const operator = FILTER_OPERATORS.find(operator => operator.type === 'exists' && operator.negate);
- const params = {};
- const filter = buildFilter({ indexPattern, field, operator, params, filterBuilder });
- expect(filter).to.be.ok();
- expect(filter.meta.negate).to.be(operator.negate);
- expect(filterBuilder.buildExistsFilter.called).to.be.ok();
- });
- });
-
- describe('areIndexPatternsProvided', function () {
- it('should return false when index patterns are not provided', function () {
- expect(areIndexPatternsProvided(undefined)).to.be(false);
- expect(areIndexPatternsProvided([])).to.be(false);
- expect(areIndexPatternsProvided([undefined])).to.be(false);
- });
-
- it('should return true when index patterns are provided', function () {
- const indexPatternMock = {};
- expect(areIndexPatternsProvided([indexPatternMock])).to.be(true);
- });
- });
-
- describe('isFilterPinned', function () {
- it('should return false when the store is appState', function () {
- const filter = { $state: { store: 'appState' } };
- expect(isFilterPinned(filter, false)).to.be(false);
- expect(isFilterPinned(filter, true)).to.be(false);
- });
-
- it('should return true when the store is globalState', function () {
- const filter = { $state: { store: 'globalState' } };
- expect(isFilterPinned(filter, false)).to.be(true);
- expect(isFilterPinned(filter, true)).to.be(true);
- });
-
- it('should return the default when the store does not exist', function () {
- const filter = {};
- expect(isFilterPinned(filter, false)).to.be(false);
- expect(isFilterPinned(filter, true)).to.be(true);
- });
- });
-});
diff --git a/src/ui/public/filter_editor/lib/filter_editor_utils.js b/src/ui/public/filter_editor/lib/filter_editor_utils.js
deleted file mode 100644
index 4c0067b62193d..0000000000000
--- a/src/ui/public/filter_editor/lib/filter_editor_utils.js
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import { FILTER_OPERATORS } from './filter_operators';
-
-export function getQueryDslFromFilter(filter) {
- return _(filter)
- .omit(['meta', '$state'])
- .cloneDeep();
-}
-
-export function getFieldFromFilter(filter, indexPatterns) {
- const { index, key } = filter.meta;
- return indexPatterns.get(index)
- .then(indexPattern => indexPattern.id && indexPattern.fields.byName[key]);
-}
-
-export function getOperatorFromFilter(filter) {
- const { type, negate } = filter.meta;
- return FILTER_OPERATORS.find((operator) => {
- return operator.type === type && operator.negate === negate;
- });
-}
-
-export function getParamsFromFilter(filter) {
- const { type, key } = filter.meta;
- let params;
- if (type === 'phrase') {
- params = filter.query ? filter.query.match[key].query : filter.script.script.params.value;
- } else if (type === 'phrases') {
- params = filter.meta.params;
- } else if (type === 'range') {
- const range = filter.range ? filter.range[key] : filter.script.script.params;
- const from = _.has(range, 'gte') ? range.gte : range.gt;
- const to = _.has(range, 'lte') ? range.lte : range.lt;
- params = { from, to };
- }
- return {
- [type]: params
- };
-}
-
-export function getFilterableFields(indexPatterns) {
- return (indexPatterns || []).reduce((fields, indexPattern) => {
- const filterableFields = indexPattern.fields.filter(field => field.filterable);
- return [...fields, ...filterableFields];
- }, []);
-}
-
-export function getOperatorOptions(field) {
- const type = _.get(field, 'type');
- return FILTER_OPERATORS.filter((operator) => {
- return !operator.fieldTypes || operator.fieldTypes.includes(type);
- });
-}
-
-export function isFilterValid({ field, operator, params }) {
- if (!field || !operator) {
- return false;
- } else if (operator.type === 'phrase') {
- return _.has(params, 'phrase') && params.phrase !== '';
- } else if (operator.type === 'phrases') {
- return _.has(params, 'phrases') && params.phrases.length > 0;
- } else if (operator.type === 'range') {
- const hasFrom = _.has(params, ['range', 'from']) && params.range.from !== '';
- const hasTo = _.has(params, ['range', 'to']) && params.range.to !== '';
- return hasFrom || hasTo;
- }
- return true;
-}
-
-export function buildFilter({ indexPattern, field, operator, params, filterBuilder }) {
- let filter;
- if (operator.type === 'phrase') {
- filter = filterBuilder.buildPhraseFilter(field, params.phrase, indexPattern);
- } else if (operator.type === 'phrases') {
- filter = filterBuilder.buildPhrasesFilter(field, params.phrases, indexPattern);
- } else if (operator.type === 'range') {
- filter = filterBuilder.buildRangeFilter(field, { gte: params.range.from, lt: params.range.to }, indexPattern);
- } else if (operator.type === 'exists') {
- filter = filterBuilder.buildExistsFilter(field, indexPattern);
- }
- filter.meta.negate = operator.negate;
- return filter;
-}
-
-export function areIndexPatternsProvided(indexPatterns) {
- return _.compact(indexPatterns).length !== 0;
-}
-
-export function isFilterPinned(filter, pinnedByDefault) {
- if (!filter.hasOwnProperty('$state')) return pinnedByDefault;
- return filter.$state.store === 'globalState';
-}
diff --git a/src/ui/public/filter_editor/lib/filter_operators.js b/src/ui/public/filter_editor/lib/filter_operators.js
deleted file mode 100644
index 140928eb1912d..0000000000000
--- a/src/ui/public/filter_editor/lib/filter_operators.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-
-export const FILTER_OPERATORS = [
- {
- name: 'is',
- type: 'phrase',
- negate: false,
- },
- {
- name: 'is not',
- type: 'phrase',
- negate: true,
- },
- {
- name: 'is one of',
- type: 'phrases',
- negate: false,
- fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape']
- },
- {
- name: 'is not one of',
- type: 'phrases',
- negate: true,
- fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape']
- },
- {
- name: 'is between',
- type: 'range',
- negate: false,
- fieldTypes: ['number', 'date', 'ip'],
- },
- {
- name: 'is not between',
- type: 'range',
- negate: true,
- fieldTypes: ['number', 'date', 'ip'],
- },
- {
- name: 'exists',
- type: 'exists',
- negate: false,
- },
- {
- name: 'does not exist',
- type: 'exists',
- negate: true,
- },
-];
-
-export const FILTER_OPERATOR_TYPES = _(FILTER_OPERATORS)
- .map('type')
- .uniq()
- .value();
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_editor.html
deleted file mode 100644
index f242ce2af5a2f..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_editor.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_editor.js b/src/ui/public/filter_editor/params_editor/filter_params_editor.js
deleted file mode 100644
index d0397d4f7508e..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_editor.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { uiModules } from '../../modules';
-import template from './filter_params_editor.html';
-import './filter_params_phrase_editor';
-import './filter_params_phrases_editor';
-import './filter_params_range_editor';
-
-const module = uiModules.get('kibana');
-module.directive('filterParamsEditor', function () {
- return {
- restrict: 'E',
- template,
- scope: {
- field: '=',
- operator: '=',
- params: '='
- }
- };
-});
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_input_type.html b/src/ui/public/filter_editor/params_editor/filter_params_input_type.html
deleted file mode 100644
index 1d1a89bbb0df9..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_input_type.html
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
- {{$select.selected}}
-
-
-
-
-
-
-
-
-
-
-
- Accepted date formats
-
-
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.js b/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.js
deleted file mode 100644
index d7b166e7990f7..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import 'angular-ui-select';
-import { uiModules } from '../../modules';
-import template from './filter_params_phrase_editor.html';
-import { filterParamsPhraseController } from './filter_params_phrase_controller';
-import './filter_params_input_type';
-import '../../directives/documentation_href';
-import '../../directives/ui_select_focus_on';
-import '../../directives/focus_on';
-import '../../filters/sort_prefix_first';
-
-const module = uiModules.get('kibana');
-module.directive('filterParamsPhraseEditor', function () {
- return {
- restrict: 'E',
- template,
- scope: {
- field: '=',
- params: '='
- },
- controllerAs: 'filterParamsPhraseEditor',
- controller: filterParamsPhraseController
- };
-});
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html
deleted file mode 100644
index 4a2935c4ba0b7..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
- {{$item}}
-
-
-
-
-
-
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.js b/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.js
deleted file mode 100644
index ca4f67b28caf5..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import 'angular-ui-select';
-import { uiModules } from '../../modules';
-import template from './filter_params_phrases_editor.html';
-import { filterParamsPhraseController } from './filter_params_phrase_controller';
-import '../../directives/ui_select_focus_on';
-import '../../filters/sort_prefix_first';
-
-const module = uiModules.get('kibana');
-module.directive('filterParamsPhrasesEditor', function () {
- return {
- restrict: 'E',
- template,
- scope: {
- field: '=',
- params: '='
- },
- controllerAs: 'filterParamsPhrasesEditor',
- controller: filterParamsPhraseController
- };
-});
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_range_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_range_editor.html
deleted file mode 100644
index 483d6c7408bd8..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_range_editor.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
`;
-
- const element = $compile(template)(scope);
-
- scope.$apply(() => {
- Object.assign(scope, attrs);
- });
-
- return element;
- }
-
-
- describe('collapse filters', function () {
- let element;
-
- beforeEach(function () {
- element = create({
- filterNavToggle: {
- isOpen: false
- }
- });
- });
-
- it('should be able to collapse filters', function () {
- expect(element.hasClass('filter-panel-close')).to.be(true);
- });
-
- it('should be able to see `actions`', function () {
- expect(element.find('.filter-link.pull-right').hasClass('action-show')).to.be(true);
- });
-
- it('should be able to view the same button for `expand`', function () {
- expect(element.find('.filter-nav-link__icon').hasClass('filter-nav-link--close')).to.be(true);
- });
- });
-
- describe('expand filters', function () {
- let element;
-
- beforeEach(function () {
- element = create({
- filterNavToggle: {
- isOpen: true
- }
- });
- });
-
- it('should be able to expand filters', function () {
- expect(element.hasClass('filter-panel-close')).to.be(false);
- });
-
- it('should be able to view the `actions` at the bottom of the filter-bar', function () {
- expect(element.find('.filter-link.pull-right').hasClass('action-show')).to.be(false);
- });
-
- it('should be able to view the same button for `collapse`', function () {
- expect(element.find('.filter-nav-link__icon').hasClass('filter-nav-link--close')).to.be(false);
- });
- });
-
- });
-
- });
-});
diff --git a/src/ui/public/filter_bar/__tests__/filter_bar_click_handler.js b/src/ui/public/filter_bar/__tests__/filter_bar_click_handler.js
deleted file mode 100644
index 612956327c18f..0000000000000
--- a/src/ui/public/filter_bar/__tests__/filter_bar_click_handler.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import ngMock from 'ng_mock';
-import expect from 'expect.js';
-
-import MockState from 'fixtures/mock_state';
-import { toastNotifications } from '../../notify';
-import AggConfigResult from '../../vis/agg_config_result';
-
-import { VisProvider } from '../../vis';
-import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { FilterBarClickHandlerProvider } from '../filter_bar_click_handler';
-
-describe('filterBarClickHandler', function () {
- let setup = null;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- setup = function () {
- const Vis = Private(VisProvider);
- const createClickHandler = Private(FilterBarClickHandlerProvider);
- const indexPattern = Private(StubbedLogstashIndexPatternProvider);
-
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- { type: 'count', schema: 'metric' },
- {
- type: 'terms',
- schema: 'segment',
- params: { field: 'non-filterable' }
- }
- ]
- });
- const aggConfigResult = new AggConfigResult(vis.aggs[1], void 0, 'apache', 'apache');
-
- const $state = new MockState({ filters: [] });
- const clickHandler = createClickHandler($state);
-
- return { clickHandler, $state, aggConfigResult };
- };
- }));
-
- beforeEach(function () {
- toastNotifications.list.splice(0);
- });
-
- describe('on non-filterable fields', function () {
- it('warns about trying to filter on a non-filterable field', function () {
- const { clickHandler, aggConfigResult } = setup();
- expect(toastNotifications.list).to.have.length(0);
- clickHandler({ point: { aggConfigResult } });
- expect(toastNotifications.list).to.have.length(1);
- });
-
- it('does not warn if the event is click is being simulated', function () {
- const { clickHandler, aggConfigResult } = setup();
- expect(toastNotifications.list).to.have.length(0);
- clickHandler({ point: { aggConfigResult } }, true);
- expect(toastNotifications.list).to.have.length(0);
- });
- });
-});
diff --git a/src/ui/public/filter_bar/_global_filter_group.scss b/src/ui/public/filter_bar/_global_filter_group.scss
new file mode 100644
index 0000000000000..9ed40964a95b4
--- /dev/null
+++ b/src/ui/public/filter_bar/_global_filter_group.scss
@@ -0,0 +1,22 @@
+// SASSTODO: Probably not the right file for this selector, but temporary until the files get re-organized
+.globalQueryBar {
+ padding-bottom: $euiSizeS;
+}
+
+.globalFilterGroup__filterBar {
+ margin-top: $euiSizeM;
+}
+
+// sass-lint:disable quotes
+.globalFilterGroup__branch {
+ padding: $euiSize $euiSize $euiSizeS $euiSizeS;
+ background-repeat: no-repeat;
+ background-position: right top;
+ background-image: url("data:image/svg+xml,%0A%3Csvg width='28px' height='28px' viewBox='0 0 28 28' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='#{hexToRGB($euiColorLightShade)}'%3E%3Crect x='14' y='27' width='14' height='1'%3E%3C/rect%3E%3Crect x='0' y='0' width='1' height='14'%3E%3C/rect%3E%3C/g%3E%3C/svg%3E");
+}
+
+.globalFilterGroup__wrapper {
+ line-height: 1; // Override kuiLocalNav & kuiLocalNavRow
+ overflow: hidden;
+ transition: height $euiAnimSpeedNormal $euiAnimSlightResistance;
+}
diff --git a/src/ui/public/filter_bar/_global_filter_item.scss b/src/ui/public/filter_bar/_global_filter_item.scss
new file mode 100644
index 0000000000000..b3bc510be0c74
--- /dev/null
+++ b/src/ui/public/filter_bar/_global_filter_item.scss
@@ -0,0 +1,37 @@
+@import '@elastic/eui/src/components/form/mixins';
+@import '@elastic/eui/src/components/form/variables';
+
+.globalFilterItem {
+ line-height: $euiSizeL + $euiSizeXS;
+ border: none;
+ color: $euiTextColor;
+
+ &:not(.globalFilterItem-isDisabled) {
+ @include euiFormControlDefaultShadow;
+ }
+}
+
+.globalFilterItem-isDisabled {
+ background-color: transparentize($euiColorLightShade, .4);
+ text-decoration: line-through;
+ font-weight: $euiFontWeightRegular;
+ font-style: italic;
+}
+
+.globalFilterItem-isPinned {
+ position: relative;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: $euiSizeXS;
+ background-color: $euiColorVis0;
+ }
+}
+
+.globalFilterItem__editorForm {
+ padding: $euiSizeM;
+}
diff --git a/src/ui/public/filter_bar/_index.scss b/src/ui/public/filter_bar/_index.scss
new file mode 100644
index 0000000000000..3c57b7fe2ca3a
--- /dev/null
+++ b/src/ui/public/filter_bar/_index.scss
@@ -0,0 +1,2 @@
+@import 'global_filter_group';
+@import 'global_filter_item';
diff --git a/src/fixtures/filters/range_filter.js b/src/ui/public/filter_bar/directive.js
similarity index 71%
rename from src/fixtures/filters/range_filter.js
rename to src/ui/public/filter_bar/directive.js
index c18b6c98a5639..afb598de5a7b1 100644
--- a/src/fixtures/filters/range_filter.js
+++ b/src/ui/public/filter_bar/directive.js
@@ -17,23 +17,13 @@
* under the License.
*/
-export const rangeFilter = {
- 'meta': {
- 'index': 'logstash-*',
- 'negate': false,
- 'disabled': false,
- 'alias': null,
- 'type': 'range',
- 'key': 'bytes',
- 'value': '0 to 10'
- },
- 'range': {
- 'bytes': {
- 'gte': 0,
- 'lt': 10
- }
- },
- '$state': {
- 'store': 'appState'
- }
-};
+import 'ngreact';
+import { uiModules } from '../modules';
+import { FilterBar } from './filter_bar';
+import { injectI18nProvider } from '@kbn/i18n/react';
+
+const app = uiModules.get('app/kibana', ['react']);
+
+app.directive('filterBar', reactDirective => {
+ return reactDirective(injectI18nProvider(FilterBar));
+});
diff --git a/src/ui/public/filter_bar/filter_bar.html b/src/ui/public/filter_bar/filter_bar.html
deleted file mode 100644
index 5cd239781d192..0000000000000
--- a/src/ui/public/filter_bar/filter_bar.html
+++ /dev/null
@@ -1,149 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ {this.state.isCustomEditorOpen ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ );
+ }
+
+ private renderIndexPatternInput() {
+ if (this.props.indexPatterns.length <= 1) {
+ return '';
+ }
+ const { selectedIndexPattern } = this.state;
+ return (
+
+
+ {this.renderIndexPatternInput()}
+
+ {this.state.isCustomEditorOpen ? this.renderCustomEditor() : this.renderRegularEditor()}
+
+
+
+
+
+ {this.state.useCustomLabel && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ {this.renderFieldInput()}
+ {this.renderOperatorInput()}
+
+
+
+ );
+ }
+
+ private renderFieldInput() {
+ const { selectedIndexPattern, selectedField } = this.state;
+ const fields = selectedIndexPattern ? getFilterableFields(selectedIndexPattern) : [];
+ return (
+ {this.renderParamsEditor()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {type === 'date' ? (
+
+ {' '}
+
+
+ ) : (
+ ''
+ )}
+
+ );
+ }
+
+ private onFromChange = (value: string | number | boolean) => {
+ if (typeof value !== 'string' && typeof value !== 'number') {
+ throw new Error('Range params must be a string or number');
+ }
+ this.props.onChange({ from: value, to: get(this, 'props.value.to') });
+ };
+
+ private onToChange = (value: string | number | boolean) => {
+ if (typeof value !== 'string' && typeof value !== 'number') {
+ throw new Error('Range params must be a string or number');
+ }
+ this.props.onChange({ from: get(this, 'props.value.from'), to: value });
+ };
+}
+
+export const RangeValueInput = injectI18n(RangeValueInputUI);
diff --git a/src/ui/public/filter_bar/filter_editor/value_input_type.tsx b/src/ui/public/filter_bar/filter_editor/value_input_type.tsx
new file mode 100644
index 0000000000000..0a573c88eae70
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_editor/value_input_type.tsx
@@ -0,0 +1,120 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EuiFieldNumber, EuiFieldText, EuiSelect } from '@elastic/eui';
+import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
+import { isEmpty } from 'lodash';
+import React, { Component } from 'react';
+import { validateParams } from './lib/filter_editor_utils';
+
+interface Props {
+ value?: string | number;
+ type: string;
+ onChange: (value: string | number | boolean) => void;
+ placeholder: string;
+ intl: InjectedIntl;
+}
+
+class ValueInputTypeUI extends Component
+
+
+ ),
+ },
+ ];
+
+ return (
+
-
-
diff --git a/src/ui/public/filter_bar/filter_pill/filter_pill.js b/src/ui/public/filter_bar/filter_pill/filter_pill.js
deleted file mode 100644
index 03b19427af930..0000000000000
--- a/src/ui/public/filter_bar/filter_pill/filter_pill.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import template from './filter_pill.html';
-import { uiModules } from '../../modules';
-
-const module = uiModules.get('kibana');
-
-module.directive('filterPill', function () {
- return {
- template,
- restrict: 'E',
- scope: {
- filter: '=',
- onToggleFilter: '=',
- onPinFilter: '=',
- onInvertFilter: '=',
- onDeleteFilter: '=',
- onEditFilter: '=',
- },
- bindToController: true,
- controllerAs: 'pill',
- controller: function filterPillController() {
-
- this.activateActions = () => {
- this.areActionsActivated = true;
- };
-
- this.deactivateActions = () => {
- this.areActionsActivated = false;
- };
-
- this.isControlledByPanel = () => {
- return _.has(this.filter, 'meta.controlledBy');
- };
-
- }
- };
-});
-
diff --git a/src/ui/public/filter_bar/filter_view/index.tsx b/src/ui/public/filter_bar/filter_view/index.tsx
new file mode 100644
index 0000000000000..2421e7dcfab97
--- /dev/null
+++ b/src/ui/public/filter_bar/filter_view/index.tsx
@@ -0,0 +1,103 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EuiBadge } from '@elastic/eui';
+import { Filter, isFilterPinned } from '@kbn/es-query';
+import { i18n } from '@kbn/i18n';
+import React, { SFC } from 'react';
+import { existsOperator, isOneOfOperator } from 'ui/filter_bar/filter_editor/lib/filter_operators';
+
+interface Props {
+ filter: Filter;
+ [propName: string]: any;
+}
+
+export const FilterView: SFC
-
- NOT
- {{ pill.filter.meta.alias }}
- {{ pill.filter.meta.key }}:
- "{{ pill.filter.meta.value }}"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/ui/public/filter_editor/filter_editor.js b/src/ui/public/filter_editor/filter_editor.js
deleted file mode 100644
index ac55a253b594f..0000000000000
--- a/src/ui/public/filter_editor/filter_editor.js
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import { uiModules } from '../modules';
-import { callAfterBindingsWorkaround } from '../compat';
-import { FILTER_OPERATOR_TYPES } from './lib/filter_operators';
-import template from './filter_editor.html';
-import '../directives/documentation_href';
-import './filter_query_dsl_editor';
-import './filter_field_select';
-import './filter_operator_select';
-import './params_editor/filter_params_editor';
-import './filter_editor.less';
-import {
- getQueryDslFromFilter,
- getFieldFromFilter,
- getOperatorFromFilter,
- getParamsFromFilter,
- isFilterValid,
- buildFilter,
- areIndexPatternsProvided,
- isFilterPinned
-} from './lib/filter_editor_utils';
-import * as filterBuilder from '@kbn/es-query';
-import { keyMap } from '../utils/key_map';
-
-const module = uiModules.get('kibana');
-module.directive('filterEditor', function ($timeout, indexPatterns) {
- return {
- restrict: 'E',
- template,
- scope: {
- indexPatterns: '=',
- filter: '=',
- onDelete: '&',
- onCancel: '&',
- onSave: '&'
- },
- controllerAs: 'filterEditor',
- bindToController: true,
- controller: callAfterBindingsWorkaround(function ($scope, $element, config) {
- const pinnedByDefault = config.get('filters:pinnedByDefault');
-
- this.init = async () => {
- if (!areIndexPatternsProvided(this.indexPatterns)) {
- const defaultIndexPattern = await indexPatterns.getDefault();
- if (defaultIndexPattern) {
- this.indexPatterns = [defaultIndexPattern];
- }
- }
- const { filter } = this;
- this.alias = filter.meta.alias;
- this.isEditingQueryDsl = false;
- this.queryDsl = getQueryDslFromFilter(filter);
- if (filter.meta.isNew) {
- this.setFocus('field');
- } else {
- getFieldFromFilter(filter, indexPatterns)
- .then((field) => {
- this.setField(field);
- this.setOperator(getOperatorFromFilter(filter));
- this.params = getParamsFromFilter(filter);
- });
- }
- };
-
- $scope.$watch(() => this.filter, this.init);
- $scope.$watchCollection(() => this.filter.meta, this.init);
-
- this.setQueryDsl = (queryDsl) => {
- this.queryDsl = queryDsl;
- };
-
- this.setField = (field) => {
- this.field = field;
- this.operator = null;
- this.params = {};
- };
-
- this.onFieldSelect = (field) => {
- this.setField(field);
- this.setFocus('operator');
- };
-
- this.setOperator = (operator) => {
- this.operator = operator;
- };
-
- this.onOperatorSelect = (operator) => {
- this.setOperator(operator);
- this.setFocus('params');
- };
-
- this.setParams = (params) => {
- this.params = params;
- };
-
- this.setFocus = (name) => {
- $timeout(() => $scope.$broadcast(`focus-${name}`));
- };
-
- this.toggleEditingQueryDsl = () => {
- this.isEditingQueryDsl = !this.isEditingQueryDsl;
- };
-
- this.isQueryDslEditorVisible = () => {
- const { type, isNew } = this.filter.meta;
- return this.isEditingQueryDsl || (!isNew && !FILTER_OPERATOR_TYPES.includes(type));
- };
-
- this.isValid = () => {
- if (this.isQueryDslEditorVisible()) {
- return _.isObject(this.queryDsl);
- }
- const { field, operator, params } = this;
- return isFilterValid({ field, operator, params });
- };
-
- this.save = () => {
- const { filter, field, operator, params, alias } = this;
-
- let newFilter;
- if (this.isQueryDslEditorVisible()) {
- const meta = _.pick(filter.meta, ['negate', 'index']);
- meta.index = meta.index || this.indexPatterns[0].id;
- newFilter = Object.assign(this.queryDsl, { meta });
- } else {
- const indexPattern = field.indexPattern;
- newFilter = buildFilter({ indexPattern, field, operator, params, filterBuilder });
- }
- newFilter.meta.disabled = filter.meta.disabled;
- newFilter.meta.alias = alias;
- const isPinned = isFilterPinned(filter, pinnedByDefault);
- return this.onSave({ filter, newFilter, isPinned });
- };
-
- $element.on('keydown', (event) => {
- if (keyMap[event.keyCode] === 'escape') {
- $timeout(() => this.onCancel());
- }
- });
- })
- };
-});
diff --git a/src/ui/public/filter_editor/filter_editor.less b/src/ui/public/filter_editor/filter_editor.less
deleted file mode 100644
index b45c0030b37d3..0000000000000
--- a/src/ui/public/filter_editor/filter_editor.less
+++ /dev/null
@@ -1,36 +0,0 @@
-.filterEditor {
- position: absolute;
- width: 600px;
- z-index: 101;
-}
-
-.filterEditor__labelBar {
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.filterEditor__wideField {
- min-width: 0;
-}
-
-.filterEditorParamsInput {
- min-width: 100px;
-}
-
-.uiSelectChoices--autoWidth {
- width: auto !important;
- min-width: 100% !important;
-}
-
-.uiSelectMatch--restrictToParent .ui-select-match-item {
- max-width: 100%;
-}
-
- .uiSelectMatch--pillWithTooltip {
- display: block;
- margin-right: 16px;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- }
diff --git a/src/ui/public/filter_editor/filter_field_select.html b/src/ui/public/filter_editor/filter_field_select.html
deleted file mode 100644
index 65c45b1bae7b0..0000000000000
--- a/src/ui/public/filter_editor/filter_field_select.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
- Add
- Edit
- filter
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Filters are built using the Elasticsearch Query DSL. -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_input_type.js b/src/ui/public/filter_editor/params_editor/filter_params_input_type.js
deleted file mode 100644
index 1278e8b508be3..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_input_type.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { uiModules } from '../../modules';
-import template from './filter_params_input_type.html';
-import '../../directives/validate_date_math';
-import '../../directives/validate_ip';
-import '../../directives/string_to_number';
-
-const module = uiModules.get('kibana');
-module.directive('filterParamsInputType', function () {
- return {
- restrict: 'E',
- template,
- scope: {
- type: '=',
- placeholder: '@',
- value: '=',
- onChange: '&'
- },
- link: function (scope) {
- scope.boolOptions = [true, false];
- scope.setDefaultBool = () => {
- if (scope.value == null) {
- scope.value = scope.boolOptions[0];
- scope.onChange({
- value: scope.value
- });
- }
- };
- }
- };
-});
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_phrase_controller.js b/src/ui/public/filter_editor/params_editor/filter_params_phrase_controller.js
deleted file mode 100644
index cbcd2d8ab5223..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_phrase_controller.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-import chrome from '../../chrome';
-
-const baseUrl = chrome.addBasePath('/api/kibana/suggestions/values');
-
-export function filterParamsPhraseController($http, $scope, config) {
- const shouldSuggestValues = this.shouldSuggestValues = config.get('filterEditor:suggestValues');
-
- this.compactUnion = _.flow(_.union, _.compact);
-
- this.getValueSuggestions = _.memoize(getValueSuggestions, getFieldQueryHash);
-
- this.refreshValueSuggestions = (query) => {
- return this.getValueSuggestions($scope.field, query)
- .then(suggestions => $scope.valueSuggestions = suggestions);
- };
-
- this.refreshValueSuggestions();
-
- function getValueSuggestions(field, query) {
- if (!shouldSuggestValues || !_.get(field, 'aggregatable') || field.type !== 'string') {
- return Promise.resolve([]);
- }
-
- const params = {
- query,
- field: field.name
- };
-
- return $http.post(`${baseUrl}/${field.indexPattern.title}`, params)
- .then(response => response.data)
- .catch(() => []);
- }
-
- function getFieldQueryHash(field, query = '') {
- return `${field.indexPattern.id}/${field.name}/${query}`;
- }
-}
diff --git a/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html
deleted file mode 100644
index 36d509f28dece..0000000000000
--- a/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Accepted date formats
-
-
diff --git a/src/ui/public/index_patterns/_field.d.ts b/src/ui/public/index_patterns/_field.d.ts
new file mode 100644
index 0000000000000..749cd63d0a84c
--- /dev/null
+++ b/src/ui/public/index_patterns/_field.d.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export interface Field {
+ name: string;
+ type: string;
+ aggregatable: boolean;
+ filterable: boolean;
+ searchable: boolean;
+}
diff --git a/src/ui/public/index_patterns/_index_pattern.d.ts b/src/ui/public/index_patterns/_index_pattern.d.ts
index 0ff899839c42c..0465e35ff2a1f 100644
--- a/src/ui/public/index_patterns/_index_pattern.d.ts
+++ b/src/ui/public/index_patterns/_index_pattern.d.ts
@@ -17,11 +17,18 @@
* under the License.
*/
+import { Field } from 'ui/index_patterns/_field';
+
/**
* WARNING: these types are incomplete
*/
-export type IndexPattern = any;
+export interface IndexPattern {
+ id: string;
+ fields: Field[];
+ title: string;
+ timeFieldName?: string;
+}
export interface StaticIndexPatternField {
name: string;
diff --git a/src/ui/public/index_patterns/fixtures/index.ts b/src/ui/public/index_patterns/fixtures/index.ts
new file mode 100644
index 0000000000000..a84ab381b52d8
--- /dev/null
+++ b/src/ui/public/index_patterns/fixtures/index.ts
@@ -0,0 +1,79 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Field, IndexPattern } from '../index';
+
+export const mockFields: Field[] = [
+ {
+ name: 'machine.os',
+ type: 'string',
+ aggregatable: false,
+ searchable: false,
+ filterable: true,
+ },
+ {
+ name: 'machine.os.raw',
+ type: 'string',
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+ {
+ name: 'not.filterable',
+ type: 'string',
+ aggregatable: true,
+ searchable: false,
+ filterable: false,
+ },
+ {
+ name: 'bytes',
+ type: 'number',
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+ {
+ name: '@timestamp',
+ type: 'date',
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+ {
+ name: 'clientip',
+ type: 'ip',
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+ {
+ name: 'bool.field',
+ type: 'boolean',
+ aggregatable: true,
+ searchable: true,
+ filterable: true,
+ },
+];
+
+export const mockIndexPattern: IndexPattern = {
+ id: 'logstash-*',
+ fields: mockFields,
+ title: 'logstash-*',
+ timeFieldName: '@timestamp',
+};
diff --git a/src/ui/public/index_patterns/index.d.ts b/src/ui/public/index_patterns/index.d.ts
index 92f04543c237e..e2d7ddd8c5254 100644
--- a/src/ui/public/index_patterns/index.d.ts
+++ b/src/ui/public/index_patterns/index.d.ts
@@ -17,4 +17,9 @@
* under the License.
*/
-export { IndexPattern, StaticIndexPattern } from 'ui/index_patterns/_index_pattern';
+export {
+ IndexPattern,
+ StaticIndexPattern,
+ StaticIndexPatternField,
+} from 'ui/index_patterns/_index_pattern';
+export { Field } from 'ui/index_patterns/_field';
diff --git a/src/ui/public/index_patterns/static_utils/__tests__/index.js b/src/ui/public/index_patterns/static_utils/__tests__/index.js
index 4a81b35e96555..e825df5d294ca 100644
--- a/src/ui/public/index_patterns/static_utils/__tests__/index.js
+++ b/src/ui/public/index_patterns/static_utils/__tests__/index.js
@@ -20,26 +20,50 @@
import expect from 'expect.js';
import { isFilterable } from '../index';
+const mockField = {
+ name: 'foo',
+ scripted: false,
+ searchable: true,
+ type: 'string',
+};
+
describe('static utils', () => {
describe('isFilterable', () => {
- it('should be filterable', () => {
- ['string', 'number', 'date', 'ip', 'boolean'].forEach(type => {
- expect(isFilterable({ type })).to.be(true);
+ describe('types', () => {
+ it('should return true for filterable types', () => {
+ ['string', 'number', 'date', 'ip', 'boolean'].forEach(type => {
+ expect(isFilterable({ ...mockField, type })).to.be(true);
+ });
});
- });
- it('should not be filterable', () => {
- [
- 'geo_point',
- 'geo_shape',
- 'attachment',
- 'murmur3',
- '_source',
- 'unknown',
- 'conflict',
- ].forEach(type => {
- expect(isFilterable({ type })).to.be(false);
+ it('should return false for filterable types if the field is not searchable', () => {
+ ['string', 'number', 'date', 'ip', 'boolean'].forEach(type => {
+ expect(isFilterable({ ...mockField, type, searchable: false })).to.be(false);
+ });
});
+
+ it('should return false for un-filterable types', () => {
+ [
+ 'geo_point',
+ 'geo_shape',
+ 'attachment',
+ 'murmur3',
+ '_source',
+ 'unknown',
+ 'conflict',
+ ].forEach(type => {
+ expect(isFilterable({ ...mockField, type })).to.be(false);
+ });
+ });
+ });
+
+
+ it('should return true for scripted fields', () => {
+ expect(isFilterable({ ...mockField, scripted: true, searchable: false })).to.be(true);
+ });
+
+ it('should return true for the _id field', () => {
+ expect(isFilterable({ ...mockField, name: '_id' })).to.be(true);
});
});
});
diff --git a/src/ui/public/index_patterns/static_utils/index.d.ts b/src/ui/public/index_patterns/static_utils/index.d.ts
index 6d387bb95882f..c3e02f2b20d79 100644
--- a/src/ui/public/index_patterns/static_utils/index.d.ts
+++ b/src/ui/public/index_patterns/static_utils/index.d.ts
@@ -17,13 +17,6 @@
* under the License.
*/
-import { StaticIndexPattern } from 'ui/index_patterns';
+import { Field } from 'ui/index_patterns';
-interface SavedObject {
- attributes: {
- fields: string;
- title: string;
- };
-}
-
-export function getFromLegacyIndexPattern(indexPatterns: any[]): StaticIndexPattern[];
+export function isFilterable(field: Field): boolean;
diff --git a/src/ui/public/index_patterns/static_utils/index.js b/src/ui/public/index_patterns/static_utils/index.js
index 2cf43c319b10a..2285858c40ccc 100644
--- a/src/ui/public/index_patterns/static_utils/index.js
+++ b/src/ui/public/index_patterns/static_utils/index.js
@@ -22,7 +22,7 @@ import { KBN_FIELD_TYPES } from '../../../../utils/kbn_field_types';
const filterableTypes = KBN_FIELD_TYPES.filter(type => type.filterable).map(type => type.name);
export function isFilterable(field) {
- return filterableTypes.includes(field.type);
+ return field.name === '_id' || field.scripted || (field.searchable && filterableTypes.includes(field.type));
}
export function getFromSavedObject(savedObject) {
@@ -31,14 +31,8 @@ export function getFromSavedObject(savedObject) {
}
return {
+ id: savedObject.id,
fields: JSON.parse(savedObject.attributes.fields),
title: savedObject.attributes.title,
};
}
-
-export function getFromLegacyIndexPattern(indexPatterns) {
- return indexPatterns.map(indexPattern => ({
- fields: indexPattern.fields.raw,
- title: indexPattern.title,
- }));
-}
diff --git a/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap b/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap
index fb092e9f684c0..e3465e4b50c51 100644
--- a/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap
+++ b/src/ui/public/query_bar/components/__snapshots__/query_bar.test.tsx.snap
@@ -34,7 +34,6 @@ exports[`QueryBar Should disable autoFocus on EuiFieldText when disableAutoFocus
role="form"
>
({
});
const mockIndexPattern = {
+ id: '1234',
title: 'logstash-*',
- fields: {
- raw: [
- {
- name: 'response',
- type: 'number',
- aggregatable: true,
- searchable: true,
- },
- ],
- },
+ fields: [
+ {
+ name: 'response',
+ type: 'number',
+ aggregatable: true,
+ filterable: true,
+ searchable: true,
+ },
+ ],
};
describe('QueryBar', () => {
diff --git a/src/ui/public/query_bar/components/query_bar.tsx b/src/ui/public/query_bar/components/query_bar.tsx
index af91fea37eb43..401950f8adf27 100644
--- a/src/ui/public/query_bar/components/query_bar.tsx
+++ b/src/ui/public/query_bar/components/query_bar.tsx
@@ -21,7 +21,6 @@ import { IndexPattern } from 'ui/index_patterns';
import { compact, debounce, isEqual } from 'lodash';
import React, { Component } from 'react';
-import { getFromLegacyIndexPattern } from 'ui/index_patterns/static_utils';
import { kfetch } from 'ui/kfetch';
import { PersistedLog } from 'ui/persisted_log';
import { Storage } from 'ui/storage';
@@ -74,6 +73,7 @@ interface Props {
indexPatterns: IndexPattern[];
store: Storage;
intl: InjectedIntl;
+ prepend?: any;
}
interface State {
@@ -196,7 +196,7 @@ export class QueryBarUI extends Component {
return recentSearchSuggestions;
}
- const indexPatterns = getFromLegacyIndexPattern(this.props.indexPatterns);
+ const indexPatterns = this.props.indexPatterns;
const getAutocompleteSuggestions = autocompleteProvider({ config, indexPatterns });
const { selectionStart, selectionEnd } = this.inputRef;
@@ -476,7 +476,7 @@ export class QueryBarUI extends Component {
aria-controls="typeahead-items"
>