-
+
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts
index 981855d1ee774..d9e1850cd6a24 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts
@@ -28,7 +28,7 @@ import {
import { esFilters, Filter, Query } from '../../../../../../../plugins/data/public';
import { migrateLegacyQuery } from '../../../../../../../plugins/kibana_legacy/public';
-interface AppState {
+export interface AppState {
/**
* Columns displayed in the table
*/
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss
index 7161560f8fda4..6ddd2e0eae8e3 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss
@@ -1,2 +1,2 @@
@import 'fetch_error/index';
-@import 'field_chooser/index';
+@import 'sidebar/index';
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_field_chooser.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_field_chooser.scss
deleted file mode 100644
index b05775c4ee95c..0000000000000
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_field_chooser.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-.dscFieldChooser {
- padding-left: $euiSizeS !important;
- padding-right: $euiSizeS !important;
-}
-
-.dscFieldChooser__toggle {
- color: $euiColorMediumShade;
- margin-left: $euiSizeS !important;
-}
-
-.dscFieldName {
- color: $euiColorDarkShade;
-}
-
-
-/*
- Fixes EUI known issue https://github.com/elastic/eui/issues/1749
-*/
-.dscProgressBarTooltip__anchor {
- display: block;
-}
-
-.dscToggleFieldFilterButton {
- width: calc(100% - #{$euiSizeS});
- color: $euiColorPrimary;
- padding-left: $euiSizeXS;
- margin-left: $euiSizeXS;
-}
-
-.dscFieldSearch__filterWrapper {
- flex-grow: 0;
-}
-
-.dscFieldSearch__formWrapper {
- padding: $euiSizeM;
-}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_index.scss
deleted file mode 100644
index 91daed8ea048e..0000000000000
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import 'field_chooser';
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.html
deleted file mode 100644
index 06e7cc5075411..0000000000000
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.js
deleted file mode 100644
index f7f219a287492..0000000000000
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field.js
+++ /dev/null
@@ -1,139 +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 'jquery';
-import _ from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { getServices } from '../../../kibana_services';
-import html from './discover_field.html';
-import './string_progress_bar';
-import detailsHtml from './lib/detail_views/string.html';
-
-export function createDiscoverFieldDirective($compile) {
- return {
- restrict: 'E',
- template: html,
- replace: true,
- scope: {
- field: '=',
- onAddField: '=',
- onAddFilter: '=',
- onRemoveField: '=',
- onShowDetails: '=',
- },
- link: function($scope, $elem) {
- let detailsElem;
- let detailScope;
-
- const init = function() {
- if ($scope.field.details) {
- $scope.toggleDetails($scope.field, true);
- }
-
- $scope.addRemoveButtonLabel = $scope.field.display
- ? i18n.translate('kbn.discover.fieldChooser.discoverField.removeButtonLabel', {
- defaultMessage: 'remove',
- })
- : i18n.translate('kbn.discover.fieldChooser.discoverField.addButtonLabel', {
- defaultMessage: 'add',
- });
- };
-
- const getWarnings = function(field) {
- let warnings = [];
-
- if (field.scripted) {
- warnings.push(
- i18n.translate(
- 'kbn.discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription',
- {
- defaultMessage: 'Scripted fields can take a long time to execute.',
- }
- )
- );
- }
-
- if (warnings.length > 1) {
- warnings = warnings.map(function(warning, i) {
- return (i > 0 ? '\n' : '') + (i + 1) + ' - ' + warning;
- });
- }
-
- return warnings;
- };
-
- $scope.canVisualize = getServices().capabilities.visualize.show;
-
- $scope.toggleDisplay = function(field) {
- if (field.display) {
- $scope.onRemoveField(field.name);
- } else {
- $scope.onAddField(field.name);
- }
-
- if (field.details) {
- $scope.toggleDetails(field);
- }
- };
-
- $scope.onClickToggleDetails = function onClickToggleDetails($event, field) {
- // Do nothing if the event originated from a child.
- if ($event.currentTarget !== $event.target) {
- $event.preventDefault();
- }
-
- $scope.toggleDetails(field);
- };
-
- $scope.toggleDetails = function(field, recompute) {
- if (_.isUndefined(field.details) || recompute) {
- $scope.onShowDetails(field, recompute);
- detailScope = $scope.$new();
- detailScope.warnings = getWarnings(field);
- detailScope.getBucketAriaLabel = bucket => {
- return i18n.translate('kbn.discover.fieldChooser.discoverField.bucketAriaLabel', {
- defaultMessage: 'Value: {value}',
- values: {
- value:
- bucket.display === ''
- ? i18n.translate('kbn.discover.fieldChooser.discoverField.emptyStringText', {
- defaultMessage: 'Empty string',
- })
- : bucket.display,
- },
- });
- };
-
- detailsElem = $(detailsHtml);
- $compile(detailsElem)(detailScope);
- $elem.append(detailsElem).addClass('active');
- $elem.find('.dscSidebarItem').addClass('dscSidebarItem--active');
- } else {
- delete field.details;
- detailScope.$destroy();
- detailsElem.remove();
- $elem.removeClass('active');
- $elem.find('.dscSidebarItem').removeClass('dscSidebarItem--active');
- }
- };
-
- init();
- },
- };
-}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html
deleted file mode 100644
index fd63c26aa2bb3..0000000000000
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
-
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js
deleted file mode 100644
index 398728e51862f..0000000000000
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js
+++ /dev/null
@@ -1,296 +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 $ from 'jquery';
-import rison from 'rison-node';
-import { fieldCalculator } from './lib/field_calculator';
-import './discover_field';
-import './discover_field_search_directive';
-import './discover_index_pattern_directive';
-import fieldChooserTemplate from './field_chooser.html';
-import {
- IndexPatternFieldList,
- KBN_FIELD_TYPES,
-} from '../../../../../../../../plugins/data/public';
-import { getMapsAppUrl, isFieldVisualizable, isMapsAppRegistered } from './lib/visualize_url_utils';
-import { getServices } from '../../../kibana_services';
-
-export function createFieldChooserDirective($location) {
- return {
- restrict: 'E',
- scope: {
- columns: '=',
- hits: '=',
- fieldCounts: '=',
- state: '=',
- indexPattern: '=',
- indexPatternList: '=',
- onAddField: '=',
- onAddFilter: '=',
- onRemoveField: '=',
- },
- template: fieldChooserTemplate,
- link: function($scope) {
- $scope.showFilter = false;
- $scope.toggleShowFilter = () => ($scope.showFilter = !$scope.showFilter);
- $scope.indexPatternList = _.sortBy($scope.indexPatternList, o => o.get('title'));
- const config = getServices().uiSettings;
-
- const filter = ($scope.filter = {
- props: ['type', 'aggregatable', 'searchable', 'missing', 'name'],
- defaults: {
- missing: true,
- type: 'any',
- name: '',
- },
- boolOpts: [
- { label: 'any', value: undefined },
- { label: 'yes', value: true },
- { label: 'no', value: false },
- ],
- reset: function() {
- filter.vals = _.clone(filter.defaults);
- },
- /**
- * filter for fields that are displayed / selected for the data table
- */
- isFieldFilteredAndDisplayed: function(field) {
- return field.display && isFieldFiltered(field);
- },
- /**
- * filter for fields that are not displayed / selected for the data table
- */
- isFieldFilteredAndNotDisplayed: function(field) {
- return !field.display && isFieldFiltered(field) && field.type !== '_source';
- },
- getActive: function() {
- return _.some(filter.props, function(prop) {
- return filter.vals[prop] !== filter.defaults[prop];
- });
- },
- });
-
- function isFieldFiltered(field) {
- const matchFilter = filter.vals.type === 'any' || field.type === filter.vals.type;
- const isAggregatable =
- filter.vals.aggregatable == null || field.aggregatable === filter.vals.aggregatable;
- const isSearchable =
- filter.vals.searchable == null || field.searchable === filter.vals.searchable;
- const scriptedOrMissing =
- !filter.vals.missing || field.type === '_source' || field.scripted || field.rowCount > 0;
- const matchName = !filter.vals.name || field.name.indexOf(filter.vals.name) !== -1;
-
- return matchFilter && isAggregatable && isSearchable && scriptedOrMissing && matchName;
- }
-
- $scope.setFilterValue = (name, value) => {
- filter.vals[name] = value;
- };
-
- $scope.filtersActive = 0;
-
- // set the initial values to the defaults
- filter.reset();
-
- $scope.$watchCollection('filter.vals', function() {
- filter.active = filter.getActive();
- if (filter.vals) {
- let count = 0;
- Object.keys(filter.vals).forEach(key => {
- if (key === 'missing' || key === 'name') {
- return;
- }
- const value = filter.vals[key];
- if ((value && value !== 'any') || value === false) {
- count++;
- }
- });
- $scope.filtersActive = count;
- }
- });
-
- $scope.$watchMulti(['[]fieldCounts', '[]columns', '[]hits'], function(cur, prev) {
- const newHits = cur[2] !== prev[2];
- let fields = $scope.fields;
- const columns = $scope.columns || [];
- const fieldCounts = $scope.fieldCounts;
-
- if (!fields || newHits) {
- $scope.fields = fields = getFields();
- }
-
- if (!fields) return;
-
- // group the fields into popular and up-popular lists
- _.chain(fields)
- .each(function(field) {
- field.displayOrder = _.indexOf(columns, field.name) + 1;
- field.display = !!field.displayOrder;
- field.rowCount = fieldCounts[field.name];
- })
- .sortBy(function(field) {
- return (field.count || 0) * -1;
- })
- .groupBy(function(field) {
- if (field.display) return 'selected';
- return field.count > 0 ? 'popular' : 'unpopular';
- })
- .tap(function(groups) {
- groups.selected = _.sortBy(groups.selected || [], 'displayOrder');
-
- groups.popular = groups.popular || [];
- groups.unpopular = groups.unpopular || [];
-
- // move excess popular fields to un-popular list
- const extras = groups.popular.splice(config.get('fields:popularLimit'));
- groups.unpopular = extras.concat(groups.unpopular);
- })
- .each(function(group, name) {
- $scope[name + 'Fields'] = _.sortBy(group, name === 'selected' ? 'display' : 'name');
- })
- .commit();
-
- // include undefined so the user can clear the filter
- $scope.fieldTypes = _.union(['any'], _.pluck(fields, 'type'));
- });
-
- $scope.increaseFieldCounter = function(fieldName) {
- $scope.indexPattern.popularizeField(fieldName, 1);
- };
-
- function getVisualizeUrl(field) {
- if (!$scope.state) {
- return '';
- }
-
- if (
- (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
- isMapsAppRegistered()
- ) {
- return getMapsAppUrl(field, $scope.indexPattern, $scope.state, $scope.columns);
- }
-
- let agg = {};
- const isGeoPoint = field.type === KBN_FIELD_TYPES.GEO_POINT;
- const type = isGeoPoint ? 'tile_map' : 'histogram';
- // If we're visualizing a date field, and our index is time based (and thus has a time filter),
- // then run a date histogram
- if (field.type === 'date' && $scope.indexPattern.timeFieldName === field.name) {
- agg = {
- type: 'date_histogram',
- schema: 'segment',
- params: {
- field: field.name,
- interval: 'auto',
- },
- };
- } else if (isGeoPoint) {
- agg = {
- type: 'geohash_grid',
- schema: 'segment',
- params: {
- field: field.name,
- precision: 3,
- },
- };
- } else {
- agg = {
- type: 'terms',
- schema: 'segment',
- params: {
- field: field.name,
- size: parseInt(config.get('discover:aggs:terms:size'), 10),
- orderBy: '2',
- },
- };
- }
-
- return (
- '#/visualize/create?' +
- $.param(
- _.assign(_.clone($location.search()), {
- indexPattern: $scope.state.index,
- type: type,
- _a: rison.encode({
- filters: $scope.state.filters || [],
- query: $scope.state.query || undefined,
- vis: {
- type: type,
- aggs: [{ schema: 'metric', type: 'count', id: '2' }, agg],
- },
- }),
- })
- )
- );
- }
-
- $scope.computeDetails = function(field, recompute) {
- if (_.isUndefined(field.details) || recompute) {
- field.details = {
- visualizeUrl: isFieldVisualizable(field) ? getVisualizeUrl(field) : null,
- ...fieldCalculator.getFieldValueCounts({
- hits: $scope.hits,
- field: field,
- count: 5,
- grouped: false,
- }),
- };
- _.each(field.details.buckets, function(bucket) {
- bucket.display = field.format.convert(bucket.value);
- });
- $scope.increaseFieldCounter(field, 1);
- } else {
- delete field.details;
- }
- };
-
- function getFields() {
- const prevFields = $scope.fields;
- const indexPattern = $scope.indexPattern;
- const hits = $scope.hits;
- const fieldCounts = $scope.fieldCounts;
-
- if (!indexPattern || !hits || !fieldCounts) return;
-
- const fieldSpecs = indexPattern.fields.slice(0);
- const fieldNamesInDocs = _.keys(fieldCounts);
- const fieldNamesInIndexPattern = _.map(indexPattern.fields, 'name');
-
- _.difference(fieldNamesInDocs, fieldNamesInIndexPattern).forEach(function(
- unknownFieldName
- ) {
- fieldSpecs.push({
- name: unknownFieldName,
- type: 'unknown',
- });
- });
-
- const fields = new IndexPatternFieldList(indexPattern, fieldSpecs);
-
- if (prevFields) {
- fields.forEach(function(field) {
- field.details = (prevFields.getByName(field.name) || {}).details;
- });
- }
-
- return fields;
- }
- },
- };
-}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/detail_views/string.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/detail_views/string.html
deleted file mode 100644
index 333dc472e956d..0000000000000
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/detail_views/string.html
+++ /dev/null
@@ -1,106 +0,0 @@
- );
+ return { comp, props };
+}
+
+describe('discover sidebar field', function() {
+ it('should allow selecting fields', function() {
+ const { comp, props } = getComponent();
+ findTestSubject(comp, 'fieldToggle-bytes').simulate('click');
+ expect(props.onAddField).toHaveBeenCalledWith('bytes');
+ });
+ it('should allow deselecting fields', function() {
+ const { comp, props } = getComponent(true);
+ findTestSubject(comp, 'fieldToggle-bytes').simulate('click');
+ expect(props.onRemoveField).toHaveBeenCalledWith('bytes');
+ });
+ it('should trigger onShowDetails', function() {
+ const { comp, props } = getComponent();
+ findTestSubject(comp, 'field-bytes-showDetails').simulate('click');
+ expect(props.onShowDetails).toHaveBeenCalledWith(true, props.field);
+ });
+});
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.tsx
new file mode 100644
index 0000000000000..f7acb751c487b
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.tsx
@@ -0,0 +1,186 @@
+/*
+ * 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 React from 'react';
+import { EuiButton, EuiToolTip, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { DiscoverFieldDetails } from './discover_field_details';
+import { FieldIcon } from '../../../../../../../../plugins/kibana_react/public';
+import { FieldDetails } from './types';
+import { IndexPatternField, IndexPattern } from '../../../../../../../../plugins/data/public';
+import { shortenDottedString } from '../../helpers';
+import { getFieldTypeName } from './lib/get_field_type_name';
+
+export interface DiscoverFieldProps {
+ /**
+ * The displayed field
+ */
+ field: IndexPatternField;
+ /**
+ * The currently selected index pattern
+ */
+ indexPattern: IndexPattern;
+ /**
+ * Callback to add/select the field
+ */
+ onAddField: (fieldName: string) => void;
+ /**
+ * Callback to add a filter to filter bar
+ */
+ onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
+ /**
+ * Callback to remove/deselect a the field
+ * @param fieldName
+ */
+ onRemoveField: (fieldName: string) => void;
+ /**
+ * Callback to hide/show details, buckets of the field
+ */
+ onShowDetails: (show: boolean, field: IndexPatternField) => void;
+ /**
+ * Determines, whether details of the field are displayed
+ */
+ showDetails: boolean;
+ /**
+ * Retrieve details data for the field
+ */
+ getDetails: (field: IndexPatternField) => FieldDetails;
+ /**
+ * Determines whether the field is selected
+ */
+ selected?: boolean;
+ /**
+ * Determines whether the field name is shortened test.sub1.sub2 = t.s.sub2
+ */
+ useShortDots?: boolean;
+}
+
+export function DiscoverField({
+ field,
+ indexPattern,
+ onAddField,
+ onRemoveField,
+ onAddFilter,
+ onShowDetails,
+ showDetails,
+ getDetails,
+ selected,
+ useShortDots,
+}: DiscoverFieldProps) {
+ const addLabel = i18n.translate('kbn.discover.fieldChooser.discoverField.addButtonLabel', {
+ defaultMessage: 'Add',
+ });
+ const addLabelAria = i18n.translate(
+ 'kbn.discover.fieldChooser.discoverField.addButtonAriaLabel',
+ {
+ defaultMessage: 'Add {field} to table',
+ values: { field: field.name },
+ }
+ );
+ const removeLabel = i18n.translate('kbn.discover.fieldChooser.discoverField.removeButtonLabel', {
+ defaultMessage: 'Remove',
+ });
+ const removeLabelAria = i18n.translate(
+ 'kbn.discover.fieldChooser.discoverField.removeButtonAriaLabel',
+ {
+ defaultMessage: 'Remove {field} from table',
+ values: { field: field.name },
+ }
+ );
+
+ const toggleDisplay = (f: IndexPatternField) => {
+ if (selected) {
+ onRemoveField(f.name);
+ } else {
+ onAddField(f.name);
+ }
+ };
+
+ return (
+ <>
+
+ )}
+ >
+ );
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_bucket.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_bucket.tsx
new file mode 100644
index 0000000000000..5a2b855828f53
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_bucket.tsx
@@ -0,0 +1,99 @@
+/*
+ * 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 React from 'react';
+import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { StringFieldProgressBar } from './string_progress_bar';
+import { Bucket } from './types';
+import { IndexPatternField } from '../../../../../../../../plugins/data/public';
+
+interface Props {
+ bucket: Bucket;
+ field: IndexPatternField;
+ onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
+}
+
+export function DiscoverFieldBucket({ field, bucket, onAddFilter }: Props) {
+ const emptyTxt = i18n.translate('kbn.discover.fieldChooser.detailViews.emptyStringText', {
+ defaultMessage: 'Empty string',
+ });
+ const addLabel = i18n.translate(
+ 'kbn.discover.fieldChooser.detailViews.filterValueButtonAriaLabel',
+ {
+ defaultMessage: 'Filter for {field}: "{value}"',
+ values: { value: bucket.value, field: field.name },
+ }
+ );
+ const removeLabel = i18n.translate(
+ 'kbn.discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel',
+ {
+ defaultMessage: 'Filter out {field}: "{value}"',
+ values: { value: bucket.value, field: field.name },
+ }
+ );
+
+ return (
+ <>
+
+
+
+ {bucket.display === '' ? emptyTxt : bucket.display}
+
+
+ {field.filterable && (
+
+
+ )}
+
+
+ >
+ );
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_details.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_details.tsx
new file mode 100644
index 0000000000000..6266c29745718
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_details.tsx
@@ -0,0 +1,98 @@
+/*
+ * 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 React from 'react';
+import { EuiLink, EuiSpacer, EuiIconTip, EuiText } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { DiscoverFieldBucket } from './discover_field_bucket';
+import { getWarnings } from './lib/get_warnings';
+import { Bucket, FieldDetails } from './types';
+import { IndexPatternField, IndexPattern } from '../../../../../../../../plugins/data/public';
+
+interface DiscoverFieldDetailsProps {
+ field: IndexPatternField;
+ indexPattern: IndexPattern;
+ details: FieldDetails;
+ onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
+}
+
+export function DiscoverFieldDetails({
+ field,
+ indexPattern,
+ details,
+ onAddFilter,
+}: DiscoverFieldDetailsProps) {
+ const warnings = getWarnings(field);
+
+ return (
+ );
- return comp;
+ return mountWithIntl( );
}
function findButtonGroup(component: ReactWrapper, id: string) {
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_search.tsx
similarity index 99%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.tsx
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_search.tsx
index 2910ff2825fe7..57f69693cb544 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_search.tsx
@@ -165,7 +165,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) {
}
isSelected={activeFiltersCount > 0}
quantity={activeFiltersCount}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_index_pattern.test.tsx
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_index_pattern.test.tsx
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_index_pattern.tsx
similarity index 94%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_index_pattern.tsx
index fd2f96ca83a2f..3b01d5e7a9e62 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_index_pattern.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import React, { useState, useEffect } from 'react';
-import { SavedObject } from 'kibana/server';
+import { SavedObject } from 'kibana/public';
import { IIndexPattern, IndexPatternAttributes } from 'src/plugins/data/public';
import { I18nProvider } from '@kbn/i18n/react';
@@ -65,14 +65,14 @@ export function DiscoverIndexPattern({
}
return (
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- -
-
- - - -
-
-
-
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts
deleted file mode 100644
index 21e8e9f5e39a0..0000000000000
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts
+++ /dev/null
@@ -1,110 +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 uuid from 'uuid/v4';
-// @ts-ignore
-import rison from 'rison-node';
-import {
- IFieldType,
- IIndexPattern,
- KBN_FIELD_TYPES,
-} from '../../../../../../../../../plugins/data/public';
-import { AppState } from '../../../angular/context_state';
-import { getServices } from '../../../../kibana_services';
-
-function getMapsAppBaseUrl() {
- const mapsAppVisAlias = getServices()
- .visualizations.getAliases()
- .find(({ name }) => {
- return name === 'maps';
- });
- return mapsAppVisAlias ? mapsAppVisAlias.aliasUrl : null;
-}
-
-export function isMapsAppRegistered() {
- return getServices()
- .visualizations.getAliases()
- .some(({ name }) => {
- return name === 'maps';
- });
-}
-
-export function isFieldVisualizable(field: IFieldType) {
- if (
- (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
- isMapsAppRegistered()
- ) {
- return true;
- }
- return field.visualizable;
-}
-
-export function getMapsAppUrl(
- field: IFieldType,
- indexPattern: IIndexPattern,
- appState: AppState,
- columns: string[]
-) {
- const mapAppParams = new URLSearchParams();
-
- // Copy global state
- const locationSplit = window.location.href.split('discover?');
- if (locationSplit.length > 1) {
- const discoverParams = new URLSearchParams(locationSplit[1]);
- const globalStateUrlValue = discoverParams.get('_g');
- if (globalStateUrlValue) {
- mapAppParams.set('_g', globalStateUrlValue);
- }
- }
-
- // Copy filters and query in app state
- const mapsAppState: any = {
- filters: appState.filters || [],
- };
- if (appState.query) {
- mapsAppState.query = appState.query;
- }
- // @ts-ignore
- mapAppParams.set('_a', rison.encode(mapsAppState));
-
- // create initial layer descriptor
- const hasColumns = columns && columns.length && columns[0] !== '_source';
- const supportsClustering = field.aggregatable;
- mapAppParams.set(
- 'initialLayers',
- // @ts-ignore
- rison.encode_array([
- {
- id: uuid(),
- label: indexPattern.title,
- sourceDescriptor: {
- id: uuid(),
- type: 'ES_SEARCH',
- geoField: field.name,
- tooltipProperties: hasColumns ? columns : [],
- indexPatternId: indexPattern.id,
- scalingType: supportsClustering ? 'CLUSTERS' : 'LIMIT',
- },
- visible: true,
- type: supportsClustering ? 'BLENDED_VECTOR' : 'VECTOR',
- },
- ])
- );
-
- return getServices().addBasePath(`${getMapsAppBaseUrl()}?${mapAppParams.toString()}`);
-}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/__snapshots__/discover_index_pattern.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/__snapshots__/discover_index_pattern.test.tsx.snap
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/__snapshots__/discover_index_pattern.test.tsx.snap
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/__snapshots__/discover_index_pattern.test.tsx.snap
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/_index.scss
new file mode 100644
index 0000000000000..17b0a6c9cfe4e
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/_index.scss
@@ -0,0 +1 @@
+@import './_sidebar';
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/_sidebar.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/_sidebar.scss
new file mode 100644
index 0000000000000..fe04d42d614ff
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/_sidebar.scss
@@ -0,0 +1,155 @@
+.dscSidebar__container {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ background-color: transparent;
+ border-right-color: transparent;
+ border-bottom-color: transparent;
+}
+
+.dscIndexPattern__container {
+ display: flex;
+ align-items: center;
+ height: $euiSize * 3;
+ margin-top: -$euiSizeS;
+}
+
+.dscIndexPattern__triggerButton {
+ @include euiTitle('xs');
+ line-height: $euiSizeXXL;
+}
+
+.dscFieldList {
+ list-style: none;
+ margin-bottom: 0;
+}
+
+.dscFieldList--selected,
+.dscFieldList--unpopular,
+.dscFieldList--popular {
+ padding-left: $euiSizeS;
+ padding-right: $euiSizeS;
+}
+
+.dscFieldListHeader {
+ padding: $euiSizeS $euiSizeS 0 $euiSizeS;
+ background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade);
+}
+
+.dscFieldList--popular {
+ background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade);
+}
+
+.dscFieldChooser {
+ padding-left: $euiSizeS !important;
+ padding-right: $euiSizeS !important;
+}
+
+.dscFieldChooser__toggle {
+ color: $euiColorMediumShade;
+ margin-left: $euiSizeS !important;
+}
+
+.dscSidebarItem {
+ border-top: 1px solid transparent;
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 2px;
+ cursor: pointer;
+ font-size: $euiFontSizeXS;
+ border-top: solid 1px transparent;
+ border-bottom: solid 1px transparent;
+ line-height: normal;
+
+ &:hover,
+ &:focus {
+ .dscSidebarItem__action {
+ opacity: 1;
+ }
+ }
+}
+
+.dscSidebarItem--active {
+ border-top: 1px solid $euiColorLightShade;
+ background: shade($euiColorLightestShade, 5%);
+ color: $euiColorFullShade;
+ .euiText {
+ font-weight: bold;
+ }
+}
+
+.dscSidebarField {
+ padding: $euiSizeXS 0;
+ display: flex;
+ align-items: flex-start;
+ max-width: 100%;
+ margin: 0;
+ width: 100%;
+ border: none;
+ border-radius: 0;
+ text-align: left;
+}
+
+.dscSidebarField__name {
+ margin-left: $euiSizeS;
+ flex-grow: 1;
+}
+
+.dscSidebarField__fieldIcon {
+ margin-top: $euiSizeXS / 2;
+ margin-right: $euiSizeXS / 2;
+}
+
+/**
+ * 1. Only visually hide the action, so that it's still accessible to screen readers.
+ * 2. When tabbed to, this element needs to be visible for keyboard accessibility.
+ */
+.dscSidebarItem__action {
+ opacity: 0; /* 1 */
+
+ &:focus {
+ opacity: 1; /* 2 */
+ }
+ font-size: 12px;
+ padding: 2px 6px !important;
+ height: 22px !important;
+ min-width: auto !important;
+ .euiButton__content {
+ padding: 0 4px;
+ }
+}
+
+/*
+ Fixes EUI known issue https://github.com/elastic/eui/issues/1749
+*/
+.dscProgressBarTooltip__anchor {
+ display: block;
+}
+
+
+.dscFieldSearch {
+ padding: $euiSizeS;
+}
+
+.dscFieldSearch__toggleButton {
+ width: calc(100% - #{$euiSizeS});
+ color: $euiColorPrimary;
+ padding-left: $euiSizeXS;
+ margin-left: $euiSizeXS;
+}
+
+.dscFieldSearch__filterWrapper {
+ flex-grow: 0;
+}
+
+.dscFieldSearch__formWrapper {
+ padding: $euiSizeM;
+}
+
+.dscFieldDetails {
+ padding: $euiSizeS;
+ background-color: $euiColorLightestShade;
+ color: $euiTextColor;
+ margin-bottom: $euiSizeS;
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/change_indexpattern.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/change_indexpattern.tsx
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/change_indexpattern.tsx
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/change_indexpattern.tsx
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.test.tsx
new file mode 100644
index 0000000000000..9a6bd65813d18
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field.test.tsx
@@ -0,0 +1,111 @@
+/*
+ * 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 React from 'react';
+// @ts-ignore
+import { findTestSubject } from '@elastic/eui/lib/test';
+// @ts-ignore
+import StubIndexPattern from 'test_utils/stub_index_pattern';
+// @ts-ignore
+import stubbedLogstashFields from 'fixtures/logstash_fields';
+import { mountWithIntl } from 'test_utils/enzyme_helpers';
+import { DiscoverField } from './discover_field';
+import { coreMock } from '../../../../../../../../core/public/mocks';
+import { IndexPatternField } from '../../../../../../../../plugins/data/public';
+
+jest.mock('../../../kibana_services', () => ({
+ getServices: () => ({
+ history: {
+ location: {
+ search: '',
+ },
+ },
+ capabilities: {
+ visualize: {
+ show: true,
+ },
+ },
+ uiSettings: {
+ get: (key: string) => {
+ if (key === 'fields:popularLimit') {
+ return 5;
+ } else if (key === 'shortDots:enable') {
+ return false;
+ }
+ },
+ },
+ }),
+}));
+
+function getComponent(selected = false, showDetails = false, useShortDots = false) {
+ const indexPattern = new StubIndexPattern(
+ 'logstash-*',
+ (cfg: any) => cfg,
+ 'time',
+ stubbedLogstashFields(),
+ coreMock.createStart()
+ );
+
+ const field = {
+ name: 'bytes',
+ type: 'number',
+ esTypes: ['long'],
+ count: 10,
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ format: null,
+ routes: {},
+ $$spec: {},
+ } as IndexPatternField;
+
+ const props = {
+ indexPattern,
+ field,
+ getDetails: jest.fn(),
+ onAddFilter: jest.fn(),
+ onAddField: jest.fn(),
+ onRemoveField: jest.fn(),
+ onShowDetails: jest.fn(),
+ showDetails,
+ selected,
+ useShortDots,
+ };
+ const comp = mountWithIntl(
-
-
-
-
-
- ( )
-
-
-- - - - {{::field.details.exists}} - - - {{::field.details.exists}} - - / {{::field.details.total}} - - -
- - - -{{field.details.error}}
-
-
-
-
-
-
-
-
-
-
- {{::bucket.display}}
-
-
-
-
-
-
-
-
-
- onShowDetails(!showDetails, field)}
+ onKeyPress={() => onShowDetails(!showDetails, field)}
+ data-test-subj={`field-${field.name}-showDetails`}
+ >
+
+
+
+
+
+
+ {useShortDots ? shortenDottedString(field.name) : field.displayName}
+
+
+
+
+ {field.name !== '_source' && !selected && (
+ ) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ toggleDisplay(field);
+ }}
+ data-test-subj={`fieldToggle-${field.name}`}
+ arial-label={addLabelAria}
+ >
+ {addLabel}
+
+ )}
+ {field.name !== '_source' && selected && (
+ ) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ toggleDisplay(field);
+ }}
+ data-test-subj={`fieldToggle-${field.name}`}
+ arial-label={removeLabelAria}
+ >
+ {removeLabel}
+
+ )}
+
+
+ {showDetails && (
+
+ onAddFilter(field, bucket.value, '+')}
+ aria-label={addLabel}
+ data-test-subj={`plus-${field.name}-${bucket.value}`}
+ style={{
+ minHeight: 'auto',
+ minWidth: 'auto',
+ paddingRight: 2,
+ paddingLeft: 2,
+ paddingTop: 0,
+ paddingBottom: 0,
+ }}
+ />
+ onAddFilter(field, bucket.value, '-')}
+ aria-label={removeLabel}
+ data-test-subj={`minus-${field.name}-${bucket.value}`}
+ style={{
+ minHeight: 'auto',
+ minWidth: 'auto',
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingRight: 2,
+ paddingLeft: 2,
+ }}
+ />
+
+
+ {!details.error && (
+
+ {' '}
+ {!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
+ onAddFilter('_exists_', field.name, '+')}>
+ {details.exists}
+
+ ) : (
+ {details.exists}
+ )}{' '}
+ / {details.total}{' '}
+
+
+ )}
+ {details.error && {details.error} }
+ {!details.error && (
+
+
+
+ {warnings.length > 0 && (
+
+ )}
+
+ >
+ )}
+
+ );
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_search.test.tsx
similarity index 98%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.test.tsx
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_search.test.tsx
index 5054f7b4bdad1..654df5bfa9ee9 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search.test.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_field_search.test.tsx
@@ -34,8 +34,7 @@ describe('DiscoverFieldSearch', () => {
function mountComponent(props?: Props) {
const compProps = props || defaultProps;
- const comp = mountWithIntl(
+ {details.buckets.map((bucket: Bucket, idx: number) => (
+
+ ))}
+
+ )}
+
+ {details.visualizeUrl && (
+ <>
+
+
({
+ getServices: () => ({
+ history: {
+ location: {
+ search: '',
+ },
+ },
+ capabilities: {
+ visualize: {
+ show: true,
+ },
+ },
+ uiSettings: {
+ get: (key: string) => {
+ if (key === 'fields:popularLimit') {
+ return 5;
+ } else if (key === 'shortDots:enable') {
+ return false;
+ }
+ },
+ },
+ }),
+}));
+
+function getCompProps() {
+ const indexPattern = new StubIndexPattern(
+ 'logstash-*',
+ (cfg: any) => cfg,
+ 'time',
+ stubbedLogstashFields(),
+ coreMock.createStart()
+ );
+
+ const hits = _.each(_.cloneDeep(realHits), indexPattern.flattenHit) as Array<
+ Record
+ >;
+
+ const indexPatternList = [
+ { id: '0', attributes: { title: 'b' } } as SavedObject,
+ { id: '1', attributes: { title: 'a' } } as SavedObject,
+ { id: '2', attributes: { title: 'c' } } as SavedObject,
+ ];
+
+ const fieldCounts: Record = {};
+
+ for (const hit of hits) {
+ for (const key of Object.keys(indexPattern.flattenHit(hit))) {
+ fieldCounts[key] = (fieldCounts[key] || 0) + 1;
+ }
+ }
+ return {
+ columns: ['extension'],
+ fieldCounts,
+ hits,
+ indexPatternList,
+ onAddFilter: jest.fn(),
+ onAddField: jest.fn(),
+ onRemoveField: jest.fn(),
+ selectedIndexPattern: indexPattern,
+ setIndexPattern: jest.fn(),
+ state: {},
+ };
+}
+
+describe('discover sidebar', function() {
+ let props: DiscoverSidebarProps;
+ let comp: ReactWrapper;
+
+ beforeAll(() => {
+ props = getCompProps();
+ comp = mountWithIntl( );
+ });
+
+ it('should have Selected Fields and Available Fields with Popular Fields sections', function() {
+ const popular = findTestSubject(comp, 'fieldList-popular');
+ const selected = findTestSubject(comp, 'fieldList-selected');
+ const unpopular = findTestSubject(comp, 'fieldList-unpopular');
+ expect(popular.children().length).toBe(1);
+ expect(unpopular.children().length).toBe(7);
+ expect(selected.children().length).toBe(1);
+ });
+ it('should allow selecting fields', function() {
+ findTestSubject(comp, 'fieldToggle-bytes').simulate('click');
+ expect(props.onAddField).toHaveBeenCalledWith('bytes');
+ });
+ it('should allow deselecting fields', function() {
+ findTestSubject(comp, 'fieldToggle-extension').simulate('click');
+ expect(props.onRemoveField).toHaveBeenCalledWith('extension');
+ });
+ it('should allow adding filters', function() {
+ findTestSubject(comp, 'field-extension-showDetails').simulate('click');
+ findTestSubject(comp, 'plus-extension-gif').simulate('click');
+ expect(props.onAddFilter).toHaveBeenCalled();
+ });
+});
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.tsx
new file mode 100644
index 0000000000000..5984df9c76e61
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.tsx
@@ -0,0 +1,326 @@
+/*
+ * 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 React, { useCallback, useEffect, useState, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiButtonIcon, EuiTitle } from '@elastic/eui';
+import { sortBy } from 'lodash';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { DiscoverField } from './discover_field';
+import { DiscoverIndexPattern } from './discover_index_pattern';
+import { DiscoverFieldSearch } from './discover_field_search';
+import { IndexPatternAttributes } from '../../../../../../../../plugins/data/common';
+import { SavedObject } from '../../../../../../../../core/types';
+import { groupFields } from './lib/group_fields';
+import {
+ IndexPatternFieldList,
+ IndexPatternField,
+ IndexPattern,
+} from '../../../../../../../../plugins/data/public';
+import { AppState } from '../../angular/discover_state';
+import { getDetails } from './lib/get_details';
+import { getDefaultFieldFilter, setFieldFilterProp } from './lib/field_filter';
+import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list';
+import { getServices } from '../../../kibana_services';
+
+export interface DiscoverSidebarProps {
+ /**
+ * the selected columns displayed in the doc table in discover
+ */
+ columns: string[];
+ /**
+ * a statistics of the distribution of fields in the given hits
+ */
+ fieldCounts: Record;
+ /**
+ * hits fetched from ES, displayed in the doc table
+ */
+ hits: Array>;
+ /**
+ * List of available index patterns
+ */
+ indexPatternList: Array>;
+ /**
+ * Callback function when selecting a field
+ */
+ onAddField: (fieldName: string) => void;
+ /**
+ * Callback function when adding a filter from sidebar
+ */
+ onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void;
+ /**
+ * Callback function when removing a field
+ * @param fieldName
+ */
+ onRemoveField: (fieldName: string) => void;
+ /**
+ * Currently selected index pattern
+ */
+ selectedIndexPattern: IndexPattern;
+ /**
+ * Callback function to select another index pattern
+ */
+ setIndexPattern: (id: string) => void;
+ /**
+ * Current app state, used for generating a link to visualize
+ */
+ state: AppState;
+}
+
+export function DiscoverSidebar({
+ columns,
+ fieldCounts,
+ hits,
+ indexPatternList,
+ onAddField,
+ onAddFilter,
+ onRemoveField,
+ selectedIndexPattern,
+ setIndexPattern,
+ state,
+}: DiscoverSidebarProps) {
+ const [openFieldMap, setOpenFieldMap] = useState(new Map());
+ const [showFields, setShowFields] = useState(false);
+ const [fields, setFields] = useState(null);
+ const [fieldFilterState, setFieldFilterState] = useState(getDefaultFieldFilter());
+ const services = getServices();
+
+ useEffect(() => {
+ const newFields = getIndexPatternFieldList(selectedIndexPattern, fieldCounts);
+ setFields(newFields);
+ }, [selectedIndexPattern, fieldCounts, hits]);
+
+ const onShowDetails = useCallback(
+ (show: boolean, field: IndexPatternField) => {
+ if (!show) {
+ setOpenFieldMap(new Map(openFieldMap.set(field.name, false)));
+ } else {
+ setOpenFieldMap(new Map(openFieldMap.set(field.name, true)));
+ selectedIndexPattern.popularizeField(field.name, 1);
+ }
+ },
+ [openFieldMap, selectedIndexPattern]
+ );
+ const onChangeFieldSearch = useCallback(
+ (field: string, value: string | boolean | undefined) => {
+ const newState = setFieldFilterProp(fieldFilterState, field, value);
+ setFieldFilterState(newState);
+ },
+ [fieldFilterState]
+ );
+
+ const getDetailsByField = useCallback(
+ (ipField: IndexPatternField) =>
+ getDetails(ipField, selectedIndexPattern, state, columns, hits, services),
+ [selectedIndexPattern, state, columns, hits, services]
+ );
+
+ const popularLimit = services.uiSettings.get('fields:popularLimit');
+ const useShortDots = services.uiSettings.get('shortDots:enable');
+
+ const {
+ selected: selectedFields,
+ popular: popularFields,
+ unpopular: unpopularFields,
+ } = useMemo(() => groupFields(fields, columns, popularLimit, fieldCounts, fieldFilterState), [
+ fields,
+ columns,
+ popularLimit,
+ fieldCounts,
+ fieldFilterState,
+ ]);
+
+ const fieldTypes = useMemo(() => {
+ const result = ['any'];
+ if (Array.isArray(fields)) {
+ for (const field of fields) {
+ if (result.indexOf(field.type) === -1) {
+ result.push(field.type);
+ }
+ }
+ }
+ return result;
+ }, [fields]);
+
+ if (!selectedIndexPattern || !fields) {
+ return null;
+ }
+
+ return (
+
+ o.attributes.title)}
+ />
+
+ );
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern_directive.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar_directive.ts
similarity index 61%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern_directive.tsx
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar_directive.ts
index d6527b0d7beed..9dcb459f83613 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern_directive.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar_directive.ts
@@ -16,25 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React from 'react';
import { wrapInI18nContext } from '../../../kibana_services';
-import { DiscoverIndexPattern, DiscoverIndexPatternProps } from './discover_index_pattern';
+import { DiscoverSidebar } from './discover_sidebar';
-/**
- * At initial rendering the angular directive the selectedIndexPattern prop is undefined
- * This wrapper catches this, had to be introduced to satisfy eslint
- */
-export function DiscoverIndexPatternWrapper(props: DiscoverIndexPatternProps) {
- if (!props.selectedIndexPattern || !Array.isArray(props.indexPatternList)) {
- return null;
- }
- return ;
-}
-
-export function createIndexPatternSelectDirective(reactDirective: any) {
- return reactDirective(wrapInI18nContext(DiscoverIndexPatternWrapper), [
+export function createDiscoverSidebarDirective(reactDirective: any) {
+ return reactDirective(wrapInI18nContext(DiscoverSidebar), [
+ ['columns', { watchDepth: 'reference' }],
+ ['fieldCounts', { watchDepth: 'reference' }],
+ ['hits', { watchDepth: 'reference' }],
['indexPatternList', { watchDepth: 'reference' }],
+ ['onAddField', { watchDepth: 'reference' }],
+ ['onAddFilter', { watchDepth: 'reference' }],
+ ['onRemoveField', { watchDepth: 'reference' }],
['selectedIndexPattern', { watchDepth: 'reference' }],
['setIndexPattern', { watchDepth: 'reference' }],
+ ['state', { watchDepth: 'reference' }],
]);
}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search_directive.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/index.ts
similarity index 67%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search_directive.ts
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/index.ts
index 6d570349ee0c6..1b837840b52f6 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_field_search_directive.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/index.ts
@@ -16,13 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { wrapInI18nContext } from '../../../kibana_services';
-import { DiscoverFieldSearch } from './discover_field_search';
-export function createFieldSearchDirective(reactDirective: any) {
- return reactDirective(wrapInI18nContext(DiscoverFieldSearch), [
- ['onChange', { watchDepth: 'reference' }],
- ['value', { watchDepth: 'value' }],
- ['types', { watchDepth: 'value' }],
- ]);
-}
+export { DiscoverSidebar } from './discover_sidebar';
+export { createDiscoverSidebarDirective } from './discover_sidebar_directive';
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_calculator.js
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/field_calculator.js
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_calculator.js
diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_calculator.test.ts
similarity index 64%
rename from src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_calculator.test.ts
index f302d684135f6..98763937e888f 100644
--- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_calculator.test.ts
@@ -18,25 +18,29 @@
*/
import _ from 'lodash';
-import { pluginInstance } from 'plugins/kibana/discover/legacy';
-import ngMock from 'ng_mock';
-import { fieldCalculator } from '../../np_ready/components/field_chooser/lib/field_calculator';
-import expect from '@kbn/expect';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-
-// Load the kibana app dependencies.
-
-let indexPattern;
+// @ts-ignore
+import realHits from 'fixtures/real_hits.js';
+// @ts-ignore
+import StubIndexPattern from 'test_utils/stub_index_pattern';
+// @ts-ignore
+import stubbedLogstashFields from 'fixtures/logstash_fields';
+import { coreMock } from '../../../../../../../../../core/public/mocks';
+import { IndexPattern } from '../../../../../../../../../plugins/data/public';
+// @ts-ignore
+import { fieldCalculator } from './field_calculator';
+
+let indexPattern: IndexPattern;
describe('fieldCalculator', function() {
- beforeEach(() => pluginInstance.initializeInnerAngular());
- beforeEach(ngMock.module('app/discover'));
- beforeEach(
- ngMock.inject(function(Private) {
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- })
- );
-
+ beforeEach(function() {
+ indexPattern = new StubIndexPattern(
+ 'logstash-*',
+ (cfg: any) => cfg,
+ 'time',
+ stubbedLogstashFields(),
+ coreMock.createStart()
+ );
+ });
it('should have a _countMissing that counts nulls & undefineds in an array', function() {
const values = [
['foo', 'bar'],
@@ -52,13 +56,13 @@ describe('fieldCalculator', function() {
'foo',
undefined,
];
- expect(fieldCalculator._countMissing(values)).to.be(5);
+ expect(fieldCalculator._countMissing(values)).toBe(5);
});
describe('_groupValues', function() {
- let groups;
- let params;
- let values;
+ let groups: Record;
+ let params: any;
+ let values: any;
beforeEach(function() {
values = [
['foo', 'bar'],
@@ -79,36 +83,36 @@ describe('fieldCalculator', function() {
});
it('should have a _groupValues that counts values', function() {
- expect(groups).to.be.an(Object);
+ expect(groups).toBeInstanceOf(Object);
});
it('should throw an error if any value is a plain object', function() {
expect(function() {
fieldCalculator._groupValues([{}, true, false], params);
- }).to.throwError();
+ }).toThrowError();
});
it('should handle values with dots in them', function() {
values = ['0', '0.........', '0.......,.....'];
params = {};
groups = fieldCalculator._groupValues(values, params);
- expect(groups[values[0]].count).to.be(1);
- expect(groups[values[1]].count).to.be(1);
- expect(groups[values[2]].count).to.be(1);
+ expect(groups[values[0]].count).toBe(1);
+ expect(groups[values[1]].count).toBe(1);
+ expect(groups[values[2]].count).toBe(1);
});
it('should have a a key for value in the array when not grouping array terms', function() {
- expect(_.keys(groups).length).to.be(3);
- expect(groups.foo).to.be.a(Object);
- expect(groups.bar).to.be.a(Object);
- expect(groups.baz).to.be.a(Object);
+ expect(_.keys(groups).length).toBe(3);
+ expect(groups.foo).toBeInstanceOf(Object);
+ expect(groups.bar).toBeInstanceOf(Object);
+ expect(groups.baz).toBeInstanceOf(Object);
});
it('should count array terms independently', function() {
- expect(groups['foo,bar']).to.be(undefined);
- expect(groups.foo.count).to.be(5);
- expect(groups.bar.count).to.be(3);
- expect(groups.baz.count).to.be(1);
+ expect(groups['foo,bar']).toBe(undefined);
+ expect(groups.foo.count).toBe(5);
+ expect(groups.bar.count).toBe(3);
+ expect(groups.baz.count).toBe(1);
});
describe('grouped array terms', function() {
@@ -118,27 +122,27 @@ describe('fieldCalculator', function() {
});
it('should group array terms when passed params.grouped', function() {
- expect(_.keys(groups).length).to.be(4);
- expect(groups['foo,bar']).to.be.a(Object);
+ expect(_.keys(groups).length).toBe(4);
+ expect(groups['foo,bar']).toBeInstanceOf(Object);
});
it('should contain the original array as the value', function() {
- expect(groups['foo,bar'].value).to.eql(['foo', 'bar']);
+ expect(groups['foo,bar'].value).toEqual(['foo', 'bar']);
});
it('should count the pairs separately from the values they contain', function() {
- expect(groups['foo,bar'].count).to.be(2);
- expect(groups.foo.count).to.be(3);
- expect(groups.bar.count).to.be(1);
+ expect(groups['foo,bar'].count).toBe(2);
+ expect(groups.foo.count).toBe(3);
+ expect(groups.bar.count).toBe(1);
});
});
});
describe('getFieldValues', function() {
- let hits;
+ let hits: any;
beforeEach(function() {
- hits = _.each(require('fixtures/real_hits.js'), indexPattern.flattenHit);
+ hits = _.each(_.cloneDeep(realHits), indexPattern.flattenHit);
});
it('Should return an array of values for _source fields', function() {
@@ -146,32 +150,32 @@ describe('fieldCalculator', function() {
hits,
indexPattern.fields.getByName('extension')
);
- expect(extensions).to.be.an(Array);
+ expect(extensions).toBeInstanceOf(Array);
expect(
_.filter(extensions, function(v) {
return v === 'html';
}).length
- ).to.be(8);
- expect(_.uniq(_.clone(extensions)).sort()).to.eql(['gif', 'html', 'php', 'png']);
+ ).toBe(8);
+ expect(_.uniq(_.clone(extensions)).sort()).toEqual(['gif', 'html', 'php', 'png']);
});
it('Should return an array of values for core meta fields', function() {
const types = fieldCalculator.getFieldValues(hits, indexPattern.fields.getByName('_type'));
- expect(types).to.be.an(Array);
+ expect(types).toBeInstanceOf(Array);
expect(
_.filter(types, function(v) {
return v === 'apache';
}).length
- ).to.be(18);
- expect(_.uniq(_.clone(types)).sort()).to.eql(['apache', 'nginx']);
+ ).toBe(18);
+ expect(_.uniq(_.clone(types)).sort()).toEqual(['apache', 'nginx']);
});
});
describe('getFieldValueCounts', function() {
- let params;
+ let params: { hits: any; field: any; count: number };
beforeEach(function() {
params = {
- hits: require('fixtures/real_hits.js'),
+ hits: _.cloneDeep(realHits),
field: indexPattern.fields.getByName('extension'),
count: 3,
};
@@ -179,36 +183,36 @@ describe('fieldCalculator', function() {
it('counts the top 3 values', function() {
const extensions = fieldCalculator.getFieldValueCounts(params);
- expect(extensions).to.be.an(Object);
- expect(extensions.buckets).to.be.an(Array);
- expect(extensions.buckets.length).to.be(3);
- expect(_.pluck(extensions.buckets, 'value')).to.eql(['html', 'php', 'gif']);
- expect(extensions.error).to.be(undefined);
+ expect(extensions).toBeInstanceOf(Object);
+ expect(extensions.buckets).toBeInstanceOf(Array);
+ expect(extensions.buckets.length).toBe(3);
+ expect(_.pluck(extensions.buckets, 'value')).toEqual(['html', 'php', 'gif']);
+ expect(extensions.error).toBe(undefined);
});
it('fails to analyze geo and attachment types', function() {
params.field = indexPattern.fields.getByName('point');
- expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
+ expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined);
params.field = indexPattern.fields.getByName('area');
- expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
+ expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined);
params.field = indexPattern.fields.getByName('request_body');
- expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
+ expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined);
});
it('fails to analyze fields that are in the mapping, but not the hits', function() {
params.field = indexPattern.fields.getByName('ip');
- expect(fieldCalculator.getFieldValueCounts(params).error).to.not.be(undefined);
+ expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined);
});
it('counts the total hits', function() {
- expect(fieldCalculator.getFieldValueCounts(params).total).to.be(params.hits.length);
+ expect(fieldCalculator.getFieldValueCounts(params).total).toBe(params.hits.length);
});
it('counts the hits the field exists in', function() {
params.field = indexPattern.fields.getByName('phpmemory');
- expect(fieldCalculator.getFieldValueCounts(params).exists).to.be(5);
+ expect(fieldCalculator.getFieldValueCounts(params).exists).toBe(5);
});
});
});
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_filter.test.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_filter.test.ts
new file mode 100644
index 0000000000000..ca0fcfc846362
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_filter.test.ts
@@ -0,0 +1,96 @@
+/*
+ * 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 { getDefaultFieldFilter, setFieldFilterProp, isFieldFiltered } from './field_filter';
+import { IndexPatternField } from '../../../../../../../../../plugins/data/public';
+
+describe('field_filter', function() {
+ it('getDefaultFieldFilter should return default filter state', function() {
+ expect(getDefaultFieldFilter()).toMatchInlineSnapshot(`
+ Object {
+ "aggregatable": null,
+ "missing": true,
+ "name": "",
+ "searchable": null,
+ "type": "any",
+ }
+ `);
+ });
+ it('setFieldFilterProp should return allow filter changes', function() {
+ const state = getDefaultFieldFilter();
+ const targetState = {
+ aggregatable: true,
+ missing: true,
+ name: 'test',
+ searchable: true,
+ type: 'string',
+ };
+ const actualState = Object.entries(targetState).reduce((acc, kv) => {
+ return setFieldFilterProp(acc, kv[0], kv[1]);
+ }, state);
+ expect(actualState).toMatchInlineSnapshot(`
+ Object {
+ "aggregatable": true,
+ "missing": true,
+ "name": "test",
+ "searchable": true,
+ "type": "string",
+ }
+ `);
+ });
+ it('filters a given list', () => {
+ const defaultState = getDefaultFieldFilter();
+ const fieldList = [
+ {
+ name: 'bytes',
+ type: 'number',
+ esTypes: ['long'],
+ count: 10,
+ scripted: false,
+ searchable: false,
+ aggregatable: false,
+ },
+ {
+ name: 'extension',
+ type: 'string',
+ esTypes: ['text'],
+ count: 10,
+ scripted: true,
+ searchable: true,
+ aggregatable: true,
+ },
+ ] as IndexPatternField[];
+
+ [
+ { filter: {}, result: ['bytes', 'extension'] },
+ { filter: { name: 'by' }, result: ['bytes'] },
+ { filter: { aggregatable: true }, result: ['extension'] },
+ { filter: { aggregatable: true, searchable: false }, result: [] },
+ { filter: { type: 'string' }, result: ['extension'] },
+ ].forEach(test => {
+ const filtered = fieldList
+ .filter(field =>
+ isFieldFiltered(field, { ...defaultState, ...test.filter }, { bytes: 1, extension: 1 })
+ )
+ .map(field => field.name);
+
+ expect(filtered).toEqual(test.result);
+ });
+ });
+});
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_filter.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_filter.ts
new file mode 100644
index 0000000000000..ed7ad1a43fa8c
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/field_filter.ts
@@ -0,0 +1,78 @@
+/*
+ * 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 { IndexPatternField } from '../../../../../../../../../plugins/data/public';
+
+export interface FieldFilterState {
+ missing: boolean;
+ type: string;
+ name: string;
+ aggregatable: null | boolean;
+ searchable: null | boolean;
+}
+
+export function getDefaultFieldFilter(): FieldFilterState {
+ return {
+ missing: true,
+ type: 'any',
+ name: '',
+ aggregatable: null,
+ searchable: null,
+ };
+}
+
+export function setFieldFilterProp(
+ state: FieldFilterState,
+ name: string,
+ value: string | boolean | null | undefined
+): FieldFilterState {
+ const newState = { ...state };
+ if (name === 'missing') {
+ newState.missing = Boolean(value);
+ } else if (name === 'aggregatable') {
+ newState.aggregatable = typeof value !== 'boolean' ? null : value;
+ } else if (name === 'searchable') {
+ newState.searchable = typeof value !== 'boolean' ? null : value;
+ } else if (name === 'name') {
+ newState.name = String(value);
+ } else if (name === 'type') {
+ newState.type = String(value);
+ }
+ return newState;
+}
+
+export function isFieldFiltered(
+ field: IndexPatternField,
+ filterState: FieldFilterState,
+ fieldCounts: Record
+): boolean {
+ const matchFilter = filterState.type === 'any' || field.type === filterState.type;
+ const isAggregatable =
+ filterState.aggregatable === null || field.aggregatable === filterState.aggregatable;
+ const isSearchable =
+ filterState.searchable === null || field.searchable === filterState.searchable;
+ const scriptedOrMissing =
+ !filterState.missing ||
+ field.type === '_source' ||
+ field.scripted ||
+ fieldCounts[field.name] > 0;
+ const matchName = !filterState.name || field.name.indexOf(filterState.name) !== -1;
+
+ return matchFilter && isAggregatable && isSearchable && scriptedOrMissing && matchName;
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_details.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_details.ts
new file mode 100644
index 0000000000000..9999b108c5cc1
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_details.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { getVisualizeUrl, isFieldVisualizable } from './visualize_url_utils';
+import { AppState } from '../../../angular/discover_state';
+// @ts-ignore
+import { fieldCalculator } from './field_calculator';
+import { IndexPatternField, IndexPattern } from '../../../../../../../../../plugins/data/public';
+import { DiscoverServices } from '../../../../build_services';
+
+export function getDetails(
+ field: IndexPatternField,
+ indexPattern: IndexPattern,
+ state: AppState,
+ columns: string[],
+ hits: Array>,
+ services: DiscoverServices
+) {
+ const details = {
+ visualizeUrl:
+ services.capabilities.visualize.show && isFieldVisualizable(field, services.visualizations)
+ ? getVisualizeUrl(field, indexPattern, state, columns, services)
+ : null,
+ ...fieldCalculator.getFieldValueCounts({
+ hits,
+ field,
+ count: 5,
+ grouped: false,
+ }),
+ };
+ if (details.buckets) {
+ for (const bucket of details.buckets) {
+ bucket.display = field.format.convert(bucket.value);
+ }
+ }
+ return details;
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_field_type_name.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_field_type_name.ts
new file mode 100644
index 0000000000000..0cf428ee48b9d
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_field_type_name.ts
@@ -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 { i18n } from '@kbn/i18n';
+
+export function getFieldTypeName(type: string) {
+ switch (type) {
+ case 'boolean':
+ return i18n.translate('kbn.discover.fieldNameIcons.booleanAriaLabel', {
+ defaultMessage: 'Boolean field',
+ });
+ case 'conflict':
+ return i18n.translate('kbn.discover.fieldNameIcons.conflictFieldAriaLabel', {
+ defaultMessage: 'Conflicting field',
+ });
+ case 'date':
+ return i18n.translate('kbn.discover.fieldNameIcons.dateFieldAriaLabel', {
+ defaultMessage: 'Date field',
+ });
+ case 'geo_point':
+ return i18n.translate('kbn.discover.fieldNameIcons.geoPointFieldAriaLabel', {
+ defaultMessage: 'Geo point field',
+ });
+ case 'geo_shape':
+ return i18n.translate('kbn.discover.fieldNameIcons.geoShapeFieldAriaLabel', {
+ defaultMessage: 'Geo shape field',
+ });
+ case 'ip':
+ return i18n.translate('kbn.discover.fieldNameIcons.ipAddressFieldAriaLabel', {
+ defaultMessage: 'IP address field',
+ });
+ case 'murmur3':
+ return i18n.translate('kbn.discover.fieldNameIcons.murmur3FieldAriaLabel', {
+ defaultMessage: 'Murmur3 field',
+ });
+ case 'number':
+ return i18n.translate('kbn.discover.fieldNameIcons.numberFieldAriaLabel', {
+ defaultMessage: 'Number field',
+ });
+ case 'source':
+ // Note that this type is currently not provided, type for _source is undefined
+ return i18n.translate('kbn.discover.fieldNameIcons.sourceFieldAriaLabel', {
+ defaultMessage: 'Source field',
+ });
+ case 'string':
+ return i18n.translate('kbn.discover.fieldNameIcons.stringFieldAriaLabel', {
+ defaultMessage: 'String field',
+ });
+ case 'nested':
+ return i18n.translate('kbn.discover.fieldNameIcons.nestedFieldAriaLabel', {
+ defaultMessage: 'Nested field',
+ });
+ default:
+ return i18n.translate('kbn.discover.fieldNameIcons.unknownFieldAriaLabel', {
+ defaultMessage: 'Unknown field',
+ });
+ }
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_index_pattern_field_list.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_index_pattern_field_list.ts
new file mode 100644
index 0000000000000..1b906501c6fe7
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_index_pattern_field_list.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { difference, map } from 'lodash';
+import {
+ IndexPatternFieldList,
+ IndexPattern,
+ IndexPatternField,
+} from '../../../../../../../../../plugins/data/public';
+
+export function getIndexPatternFieldList(
+ indexPattern: IndexPattern,
+ fieldCounts: Record
+): IndexPatternFieldList {
+ if (!indexPattern || !fieldCounts) return new IndexPatternFieldList(indexPattern, []);
+
+ const fieldSpecs = indexPattern.fields.slice(0);
+ const fieldNamesInDocs = Object.keys(fieldCounts);
+ const fieldNamesInIndexPattern = map(indexPattern.fields, 'name');
+
+ difference(fieldNamesInDocs, fieldNamesInIndexPattern).forEach(unknownFieldName => {
+ fieldSpecs.push({
+ name: String(unknownFieldName),
+ type: 'unknown',
+ } as IndexPatternField);
+ });
+
+ return new IndexPatternFieldList(indexPattern, fieldSpecs);
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_warnings.ts
similarity index 55%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_warnings.ts
index 47e50f3cc3d4b..51d18c03888a7 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/get_warnings.ts
@@ -16,20 +16,28 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { FieldName } from '../../../../../../../../plugins/discover/public';
-import { getServices, wrapInI18nContext } from '../../../kibana_services';
+import { i18n } from '@kbn/i18n';
+import { IndexPatternField } from '../../../../../../../../../plugins/data/public';
-export function FieldNameDirectiveProvider(reactDirective) {
- return reactDirective(
- wrapInI18nContext(FieldName),
- [
- ['field', { watchDepth: 'collection' }],
- ['fieldName', { watchDepth: 'reference' }],
- ['fieldType', { watchDepth: 'reference' }],
- ],
- { restrict: 'AE' },
- {
- useShortDots: getServices().uiSettings.get('shortDots:enable'),
- }
- );
+export function getWarnings(field: IndexPatternField) {
+ let warnings = [];
+
+ if (field.scripted) {
+ warnings.push(
+ i18n.translate(
+ 'kbn.discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription',
+ {
+ defaultMessage: 'Scripted fields can take a long time to execute.',
+ }
+ )
+ );
+ }
+
+ if (warnings.length > 1) {
+ warnings = warnings.map(function(warning, i) {
+ return (i > 0 ? '\n' : '') + (i + 1) + ' - ' + warning;
+ });
+ }
+
+ return warnings;
}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/group_fields.test.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/group_fields.test.ts
new file mode 100644
index 0000000000000..e83287a139dd0
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/group_fields.test.ts
@@ -0,0 +1,114 @@
+/*
+ * 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 { groupFields } from './group_fields';
+import { getDefaultFieldFilter } from './field_filter';
+
+describe('group_fields', function() {
+ it('should group fields in selected, popular, unpopular group', function() {
+ const fields = [
+ {
+ name: 'category',
+ type: 'string',
+ esTypes: ['text'],
+ count: 1,
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ {
+ name: 'currency',
+ type: 'string',
+ esTypes: ['keyword'],
+ count: 0,
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ {
+ name: 'customer_birth_date',
+ type: 'date',
+ esTypes: ['date'],
+ count: 0,
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ ];
+
+ const fieldCounts = {
+ category: 1,
+ currency: 1,
+ customer_birth_date: 1,
+ };
+
+ const fieldFilterState = getDefaultFieldFilter();
+
+ const actual = groupFields(fields as any, ['currency'], 5, fieldCounts, fieldFilterState);
+ expect(actual).toMatchInlineSnapshot(`
+ Object {
+ "popular": Array [
+ Object {
+ "aggregatable": true,
+ "count": 1,
+ "esTypes": Array [
+ "text",
+ ],
+ "name": "category",
+ "readFromDocValues": true,
+ "scripted": false,
+ "searchable": true,
+ "type": "string",
+ },
+ ],
+ "selected": Array [
+ Object {
+ "aggregatable": true,
+ "count": 0,
+ "esTypes": Array [
+ "keyword",
+ ],
+ "name": "currency",
+ "readFromDocValues": true,
+ "scripted": false,
+ "searchable": true,
+ "type": "string",
+ },
+ ],
+ "unpopular": Array [
+ Object {
+ "aggregatable": true,
+ "count": 0,
+ "esTypes": Array [
+ "date",
+ ],
+ "name": "customer_birth_date",
+ "readFromDocValues": true,
+ "scripted": false,
+ "searchable": true,
+ "type": "date",
+ },
+ ],
+ }
+ `);
+ });
+});
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/group_fields.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/group_fields.tsx
new file mode 100644
index 0000000000000..85ca8d6a4e15e
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/group_fields.tsx
@@ -0,0 +1,78 @@
+/*
+ * 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 {
+ IndexPatternFieldList,
+ IndexPatternField,
+} from '../../../../../../../../../plugins/data/public';
+import { FieldFilterState, isFieldFiltered } from './field_filter';
+
+interface GroupedFields {
+ selected: IndexPatternField[];
+ popular: IndexPatternField[];
+ unpopular: IndexPatternField[];
+}
+
+/**
+ * group the fields into selected, popular and unpopular, filter by fieldFilterState
+ */
+export function groupFields(
+ fields: IndexPatternFieldList | null,
+ columns: string[],
+ popularLimit: number,
+ fieldCounts: Record,
+ fieldFilterState: FieldFilterState
+): GroupedFields {
+ const result: GroupedFields = {
+ selected: [],
+ popular: [],
+ unpopular: [],
+ };
+ if (!Array.isArray(fields) || !Array.isArray(columns) || typeof fieldCounts !== 'object') {
+ return result;
+ }
+
+ const popular = fields
+ .filter(field => !columns.includes(field.name) && field.count)
+ .sort((a: IndexPatternField, b: IndexPatternField) => (b.count || 0) - (a.count || 0))
+ .map(field => field.name)
+ .slice(0, popularLimit);
+
+ const compareFn = (a: IndexPatternField, b: IndexPatternField) => {
+ if (!a.displayName) {
+ return 0;
+ }
+ return a.displayName.localeCompare(b.displayName || '');
+ };
+ const fieldsSorted = fields.sort(compareFn);
+
+ for (const field of fieldsSorted) {
+ if (!isFieldFiltered(field, fieldFilterState, fieldCounts)) {
+ continue;
+ }
+ if (columns.includes(field.name)) {
+ result.selected.push(field);
+ } else if (popular.includes(field.name) && field.type !== '_source') {
+ result.popular.push(field);
+ } else if (field.type !== '_source') {
+ result.unpopular.push(field);
+ }
+ }
+
+ return result;
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/visualize_url_utils.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/visualize_url_utils.ts
new file mode 100644
index 0000000000000..d146d212055b7
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/lib/visualize_url_utils.ts
@@ -0,0 +1,188 @@
+/*
+ * 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 uuid from 'uuid/v4';
+import rison from 'rison-node';
+import { parse, stringify } from 'query-string';
+import {
+ IFieldType,
+ IIndexPattern,
+ IndexPatternField,
+ KBN_FIELD_TYPES,
+} from '../../../../../../../../../plugins/data/public';
+import { AppState } from '../../../angular/discover_state';
+import { DiscoverServices } from '../../../../build_services';
+import {
+ VisualizationsStart,
+ VisTypeAlias,
+} from '../../../../../../../../../plugins/visualizations/public';
+
+function getMapsAppBaseUrl(visualizations: VisualizationsStart) {
+ const mapsAppVisAlias = visualizations.getAliases().find(({ name }) => {
+ return name === 'maps';
+ });
+ return mapsAppVisAlias ? mapsAppVisAlias.aliasUrl : null;
+}
+
+export function isMapsAppRegistered(visualizations: VisualizationsStart) {
+ return visualizations.getAliases().some(({ name }: VisTypeAlias) => {
+ return name === 'maps';
+ });
+}
+
+export function isFieldVisualizable(field: IFieldType, visualizations: VisualizationsStart) {
+ if (field.name === '_id') {
+ // Else you'd get a 'Fielddata access on the _id field is disallowed' error on ES side.
+ return false;
+ }
+ if (
+ (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
+ isMapsAppRegistered(visualizations)
+ ) {
+ return true;
+ }
+ return field.visualizable;
+}
+
+export function getMapsAppUrl(
+ field: IFieldType,
+ indexPattern: IIndexPattern,
+ appState: AppState,
+ columns: string[],
+ services: DiscoverServices
+) {
+ const mapAppParams = new URLSearchParams();
+
+ // Copy global state
+ const locationSplit = window.location.href.split('discover?');
+ if (locationSplit.length > 1) {
+ const discoverParams = new URLSearchParams(locationSplit[1]);
+ const globalStateUrlValue = discoverParams.get('_g');
+ if (globalStateUrlValue) {
+ mapAppParams.set('_g', globalStateUrlValue);
+ }
+ }
+
+ // Copy filters and query in app state
+ const mapsAppState: any = {
+ filters: appState.filters || [],
+ };
+ if (appState.query) {
+ mapsAppState.query = appState.query;
+ }
+ // @ts-ignore
+ mapAppParams.set('_a', rison.encode(mapsAppState));
+
+ // create initial layer descriptor
+ const hasColumns = columns && columns.length && columns[0] !== '_source';
+ const supportsClustering = field.aggregatable;
+ mapAppParams.set(
+ 'initialLayers',
+ // @ts-ignore
+ rison.encode_array([
+ {
+ id: uuid(),
+ label: indexPattern.title,
+ sourceDescriptor: {
+ id: uuid(),
+ type: 'ES_SEARCH',
+ geoField: field.name,
+ tooltipProperties: hasColumns ? columns : [],
+ indexPatternId: indexPattern.id,
+ scalingType: supportsClustering ? 'CLUSTERS' : 'LIMIT',
+ },
+ visible: true,
+ type: supportsClustering ? 'BLENDED_VECTOR' : 'VECTOR',
+ },
+ ])
+ );
+
+ return services.addBasePath(
+ `${getMapsAppBaseUrl(services.visualizations)}?${mapAppParams.toString()}`
+ );
+}
+
+export function getVisualizeUrl(
+ field: IndexPatternField,
+ indexPattern: IIndexPattern,
+ state: AppState,
+ columns: string[],
+ services: DiscoverServices
+) {
+ const aggsTermSize = services.uiSettings.get('discover:aggs:terms:size');
+ const urlParams = parse(services.history.location.search) as Record;
+
+ if (
+ (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
+ isMapsAppRegistered(services.visualizations)
+ ) {
+ return getMapsAppUrl(field, indexPattern, state, columns, services);
+ }
+
+ let agg;
+ const isGeoPoint = field.type === KBN_FIELD_TYPES.GEO_POINT;
+ const type = isGeoPoint ? 'tile_map' : 'histogram';
+ // If we're visualizing a date field, and our index is time based (and thus has a time filter),
+ // then run a date histogram
+ if (field.type === 'date' && indexPattern.timeFieldName === field.name) {
+ agg = {
+ type: 'date_histogram',
+ schema: 'segment',
+ params: {
+ field: field.name,
+ interval: 'auto',
+ },
+ };
+ } else if (isGeoPoint) {
+ agg = {
+ type: 'geohash_grid',
+ schema: 'segment',
+ params: {
+ field: field.name,
+ precision: 3,
+ },
+ };
+ } else {
+ agg = {
+ type: 'terms',
+ schema: 'segment',
+ params: {
+ field: field.name,
+ size: parseInt(aggsTermSize, 10),
+ orderBy: '2',
+ },
+ };
+ }
+ const linkUrlParams = {
+ ...urlParams,
+ ...{
+ indexPattern: state.index!,
+ type,
+ _a: rison.encode({
+ filters: state.filters || [],
+ query: state.query,
+ vis: {
+ type,
+ aggs: [{ schema: 'metric', type: 'count', id: '2' }, agg],
+ },
+ } as any),
+ },
+ };
+
+ return `#/visualize/create?${stringify(linkUrlParams)}`;
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/string_progress_bar.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/string_progress_bar.tsx
similarity index 80%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/string_progress_bar.tsx
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/string_progress_bar.tsx
index 0c5e7fa69357d..7ea41aa4bf270 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/string_progress_bar.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/string_progress_bar.tsx
@@ -18,14 +18,13 @@
*/
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiText, EuiToolTip } from '@elastic/eui';
-import { wrapInI18nContext } from '../../../kibana_services';
interface Props {
percent: number;
count: number;
}
-function StringFieldProgressBar(props: Props) {
+export function StringFieldProgressBar(props: Props) {
return (
-
+
@@ -50,7 +49,3 @@ function StringFieldProgressBar(props: Props) {
);
}
-
-export function createStringFieldProgressBarDirective(reactDirective: any) {
- return reactDirective(wrapInI18nContext(StringFieldProgressBar));
-}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/types.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/types.ts
similarity index 78%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/types.ts
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/types.ts
index 302bf5165777c..a1d71b4d3447d 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/types.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/types.ts
@@ -21,3 +21,18 @@ export interface IndexPatternRef {
id: string;
title: string;
}
+
+export interface FieldDetails {
+ error: string;
+ exists: number;
+ total: boolean;
+ buckets: Bucket[];
+ visualizeUrl: string;
+}
+
+export interface Bucket {
+ display: string;
+ value: string;
+ percent: number;
+ count: number;
+}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts
index 3cb8bce80fa41..f8e769d837447 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import angular from 'angular';
import _ from 'lodash';
import * as Rx from 'rxjs';
import { Subscription } from 'rxjs';
@@ -23,7 +24,7 @@ import { i18n } from '@kbn/i18n';
import {
UiActionsStart,
APPLY_FILTER_TRIGGER,
-} from '../../../../../../..//plugins/ui_actions/public';
+} from '../../../../../../../plugins/ui_actions/public';
import { RequestAdapter, Adapters } from '../../../../../../../plugins/inspector/public';
import {
esFilters,
@@ -41,7 +42,6 @@ import { ISearchEmbeddable, SearchInput, SearchOutput } from './types';
import { SortOrder } from '../angular/doc_table/components/table_header/helpers';
import { getSortForSearchSource } from '../angular/doc_table/lib/get_sort_for_search_source';
import {
- angular,
getRequestInspectorStats,
getResponseInspectorStats,
getServices,
diff --git a/src/legacy/ui/public/styles/_legacy/components/_index.scss b/src/legacy/ui/public/styles/_legacy/components/_index.scss
index 4a50a0430d559..cfae0700bb71e 100644
--- a/src/legacy/ui/public/styles/_legacy/components/_index.scss
+++ b/src/legacy/ui/public/styles/_legacy/components/_index.scss
@@ -7,7 +7,6 @@
@import './navbar';
@import './config';
@import './pagination';
-@import './sidebar';
@import './spinner';
@import './table';
@import './truncate';
diff --git a/src/legacy/ui/public/styles/_legacy/components/_sidebar.scss b/src/legacy/ui/public/styles/_legacy/components/_sidebar.scss
deleted file mode 100644
index d44129b6ec849..0000000000000
--- a/src/legacy/ui/public/styles/_legacy/components/_sidebar.scss
+++ /dev/null
@@ -1,127 +0,0 @@
-// ONLY USED IN DISCOVER
-
-.sidebar-container {
- padding-left: 0 !important;
- padding-right: 0 !important;
- background-color: $euiColorLightestShade;
- border-right-color: transparent;
- border-bottom-color: transparent;
-
- .sidebar-well {
- background-color: lightOrDarkTheme(tint($euiColorPrimary, 90%), $euiColorLightShade);
- }
-
- .sidebar-list {
- .sidebar-controls {
- border-radius: $euiBorderRadius;
- margin-right: -13px;
- margin-top: $euiSizeXS / 2;
-
- .navbar-btn-link {
- padding-left: $euiSizeS;
- padding-right: $euiSizeS;
- }
-
- .sidebar-controls-error {
- cursor: default;
- }
- }
-
- ul {
- list-style: none;
- margin-bottom: 0;
- }
-
- .sidebar-item {
- border-top-color: transparent;
- font-size: $euiFontSizeXS;
- border-top: solid 1px transparent;
- border-bottom: solid 1px transparent;
- line-height: normal;
-
- label {
- @include __legacyLabelStyles__bad;
- margin-bottom: $euiSizeXS;
- display: block;
- }
-
- &.active {
- background-color: shade($euiColorLightestShade, 10%);
- color: $euiColorDarkestShade;
- border-color: $euiColorLightShade;
- }
- }
-
- .sidebar-item-title,
- .sidebar-item-text {
- margin: 0;
- padding: $euiSizeXS 0;
- text-align: center;
- width: 100%;
- border: none;
- border-radius: 0;
- }
-
- .sidebar-item-title {
- @include euiTextTruncate;
- text-align: left;
-
- &.full-title {
- white-space: normal;
- }
- }
-
- .sidebar-item-text {
- background: $euiColorEmptyShade;
- }
- }
-
- .sidebar-list-header {
- .sidebar-list-header-heading {
- color: $euiColorDarkestShade;
- border: 1px solid transparent;
- }
-
- .sidebar-list-header-label {
- padding-left: $euiSizeS;
- font-size: $euiFontSizeXS;
- line-height: $euiLineHeight;
- font-weight: $euiFontWeightBold;
- color: $euiColorDarkShade;
- border-bottom: 1px solid $euiColorLightShade;
- }
- }
-
- .index-pattern {
- font-weight: $euiFontWeightBold;
- padding: $euiSizeXS $euiSizeS;
- display: flex;
- justify-content: space-between;
- background-color: shadeOrTint($euiColorPrimary, 60%, 60%);
- color: $euiColorEmptyShade;
- line-height: $euiSizeL;
-
- .index-pattern-label {
- font-size: $euiFontSizeS;
- font-weight: $euiFontWeightBold;
- margin: 0;
- }
-
- > * {
- flex: 0 1 auto;
- align-self: center;
- }
- }
-}
-
-.indexPattern__container {
- display: flex;
- align-items: center;
- height: $euiSize * 3;
- margin-top: -$euiSizeS;
-}
-
-.indexPattern__triggerButton {
- @include euiTitle('xs');
- line-height: $euiSizeXXL;
-}
diff --git a/src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap b/src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap
index 2424416f6f92c..9f48e6e57e0ff 100644
--- a/src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap
+++ b/src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap
@@ -2,7 +2,7 @@
exports[`FieldName renders a geo field, useShortDots is set to true 1`] = `
+
+
+
+ {fields.length > 0 && (
+ <>
+
+
+
+
+
+
+
+
+ -
+ {selectedFields.map((field: IndexPatternField, idx: number) => {
+ return (
+
-
+
+
+ );
+ })}
+
+
+
+
+
+
+ >
+ )}
+ {popularFields.length > 0 && (
+
+
+
+
+ setShowFields(!showFields)}
+ aria-label={
+ showFields
+ ? i18n.translate(
+ 'kbn.discover.fieldChooser.filter.indexAndFieldsSectionHideAriaLabel',
+ {
+ defaultMessage: 'Hide fields',
+ }
+ )
+ : i18n.translate(
+ 'kbn.discover.fieldChooser.filter.indexAndFieldsSectionShowAriaLabel',
+ {
+ defaultMessage: 'Show fields',
+ }
+ )
+ }
+ />
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ -
+ {popularFields.map((field: IndexPatternField, idx: number) => {
+ return (
+
-
+
+
+ );
+ })}
+
-
+ {unpopularFields.map((field: IndexPatternField, idx: number) => {
+ return (
+
-
+
+
+ );
+ })}
+
{
- const field = {
- type: 'number',
- name: 'test.test.test',
- rowCount: 100,
- scripted: false,
- };
- const component = render( );
+ const component = render( );
expect(component).toMatchSnapshot();
});
test('FieldName renders a geo field, useShortDots is set to true', () => {
- const field = {
- type: 'geo_point',
- name: 'test.test.test',
- rowCount: 0,
- scripted: false,
- };
- const component = render( );
+ const component = render(
+
+ );
expect(component).toMatchSnapshot();
});
diff --git a/src/plugins/discover/public/components/field_name/field_name.tsx b/src/plugins/discover/public/components/field_name/field_name.tsx
index 63518aae28de6..f7f1433328adf 100644
--- a/src/plugins/discover/public/components/field_name/field_name.tsx
+++ b/src/plugins/discover/public/components/field_name/field_name.tsx
@@ -17,51 +17,36 @@
* under the License.
*/
import React from 'react';
-import classNames from 'classnames';
import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
import { FieldIcon, FieldIconProps } from '../../../../kibana_react/public';
import { shortenDottedString } from '../../helpers';
import { getFieldTypeName } from './field_type_name';
-// property field is provided at discover's field chooser
// properties fieldType and fieldName are provided in kbn_doc_view
// this should be changed when both components are deangularized
interface Props {
- field?: {
- type: string;
- name: string;
- rowCount?: number;
- scripted?: boolean;
- };
- fieldName?: string;
- fieldType?: string;
+ fieldName: string;
+ fieldType: string;
useShortDots?: boolean;
fieldIconProps?: Omit;
+ scripted?: boolean;
}
-export function FieldName({ field, fieldName, fieldType, useShortDots, fieldIconProps }: Props) {
- const type = field ? String(field.type) : String(fieldType);
- const typeName = getFieldTypeName(type);
-
- const name = field ? String(field.name) : String(fieldName);
- const displayName = useShortDots ? shortenDottedString(name) : name;
-
- const noResults = field ? !field.rowCount && !field.scripted : false;
-
- const className = classNames('dscFieldName', {
- 'dscFieldName--noResults': noResults,
- });
+export function FieldName({
+ fieldName,
+ fieldType,
+ useShortDots,
+ fieldIconProps,
+ scripted = false,
+}: Props) {
+ const typeName = getFieldTypeName(fieldType);
+ const displayName = useShortDots ? shortenDottedString(fieldName) : fieldName;
return (
-
+
-
+
diff --git a/test/accessibility/apps/discover.ts b/test/accessibility/apps/discover.ts
index cf3d37d29b491..086b13ecee2b3 100644
--- a/test/accessibility/apps/discover.ts
+++ b/test/accessibility/apps/discover.ts
@@ -63,7 +63,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
await a11y.testAppSnapshot();
});
- it.skip('Click on new to clear the search', async () => {
+ it('Click on new to clear the search', async () => {
await PageObjects.discover.clickNewSearchButton();
await a11y.testAppSnapshot();
});
@@ -121,7 +121,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
await a11y.testAppSnapshot();
});
- it.skip('Add more fields from sidebar', async () => {
+ it('Add more fields from sidebar', async () => {
for (const [columnName, value] of TEST_FILTER_COLUMN_NAMES) {
await PageObjects.discover.clickFieldListItem(columnName);
await PageObjects.discover.clickFieldListPlusFilter(columnName, value);
diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js
index 65291c3c4772c..d85d5a952d3ec 100644
--- a/test/functional/apps/management/_scripted_fields.js
+++ b/test/functional/apps/management/_scripted_fields.js
@@ -388,7 +388,7 @@ export default function({ getService, getPageObjects }) {
await log.debug('filter by "Sep 17, 2015 @ 23:00" in the expanded scripted field list');
await PageObjects.discover.clickFieldListPlusFilter(
scriptedPainlessFieldName2,
- '2015-09-17 23:00'
+ '1442531297065'
);
await PageObjects.header.waitUntilLoadingHasFinished();
diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts
index 1d4e51360f319..10652ce3ec4b2 100644
--- a/test/functional/page_objects/discover_page.ts
+++ b/test/functional/page_objects/discover_page.ts
@@ -112,6 +112,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider
public async clickNewSearchButton() {
await testSubjects.click('discoverNewButton');
+ await header.waitUntilLoadingHasFinished();
}
public async clickSaveSearchButton() {
@@ -207,7 +208,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider
public async getAllFieldNames() {
const sidebar = await testSubjects.find('discover-sidebar');
const $ = await sidebar.parseDomContent();
- return $('.sidebar-item[attr-field]')
+ return $('.dscSidebar__item[attr-field]')
.toArray()
.map(field =>
$(field)
@@ -249,13 +250,17 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider
}
public async expectMissingFieldListItemVisualize(field: string) {
- await testSubjects.missingOrFail(`fieldVisualize-${field}`, { allowHidden: true });
+ await testSubjects.missingOrFail(`fieldVisualize-${field}`);
}
public async clickFieldListPlusFilter(field: string, value: string) {
- // this method requires the field details to be open from clickFieldListItem()
+ const plusFilterTestSubj = `plus-${field}-${value}`;
+ if (!(await testSubjects.exists(plusFilterTestSubj))) {
+ // field has to be open
+ await this.clickFieldListItem(field);
+ }
// testSubjects.find doesn't handle spaces in the data-test-subj value
- await testSubjects.click(`plus-${field}-${value}`);
+ await testSubjects.click(plusFilterTestSubj);
await header.waitUntilLoadingHasFinished();
}
diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css
index 2c203e507260f..88e38d55e5784 100644
--- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css
+++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css
@@ -34,7 +34,7 @@ filter-bar,
/* hide unusable controls */
discover-app .dscTimechart,
-discover-app .sidebar-container,
+discover-app .dscSidebar__container,
discover-app .kbnCollapsibleSidebar__collapseButton,
discover-app navbar[name=discover-search],
discover-app .discover-table-footer {
diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css
index b5c9861208b7b..ddd280c7b3f23 100644
--- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css
+++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css
@@ -33,7 +33,7 @@ filter-bar,
/* hide unusable controls */
discover-app .dscTimechart,
-discover-app .sidebar-container,
+discover-app .dscSidebar__container,
discover-app .kbnCollapsibleSidebar__collapseButton,
discover-app navbar[name="discover-search"],
discover-app .discover-table-footer {
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index ccfb2707a51b9..447a3b706bf96 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -1060,16 +1060,9 @@
"kbn.discover.fetchError.managmentLinkText": "管理 > インデックスパターン",
"kbn.discover.fetchError.scriptedFieldsText": "「スクリプトフィールド」",
"kbn.discover.fieldChooser.detailViews.emptyStringText": "空の文字列",
- "kbn.discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel": "この値を除外",
- "kbn.discover.fieldChooser.detailViews.filterValueButtonAriaLabel": "この値でフィルターを適用",
"kbn.discover.fieldChooser.detailViews.recordsText": "記録",
"kbn.discover.fieldChooser.detailViews.topValuesInRecordsDescription": "次の記録のトップ 5 の値",
"kbn.discover.fieldChooser.detailViews.visualizeLinkText": "可視化",
- "kbn.discover.fieldChooser.detailViews.warningsText": "{warningsLength, plural, one {# 警告} other {# 警告}}",
- "kbn.discover.fieldChooser.discoverField.addButtonLabel": "追加",
- "kbn.discover.fieldChooser.discoverField.bucketAriaLabel": "値: {value}",
- "kbn.discover.fieldChooser.discoverField.emptyStringText": "空の文字列",
- "kbn.discover.fieldChooser.discoverField.removeButtonLabel": "削除",
"kbn.discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription": "スクリプトフィールドは実行に時間がかかる場合があります。",
"kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "ジオフィールドは分析できません。",
"kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "オブジェクトフィールドは分析できません。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 06eb805d5af0e..5ca4f609a6dcd 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -1060,16 +1060,9 @@
"kbn.discover.fetchError.managmentLinkText": "管理 > 索引模式",
"kbn.discover.fetchError.scriptedFieldsText": "“脚本字段”",
"kbn.discover.fieldChooser.detailViews.emptyStringText": "空字符串",
- "kbn.discover.fieldChooser.detailViews.filterOutValueButtonAriaLabel": "筛除此值",
- "kbn.discover.fieldChooser.detailViews.filterValueButtonAriaLabel": "筛留此值",
"kbn.discover.fieldChooser.detailViews.recordsText": "个记录",
"kbn.discover.fieldChooser.detailViews.topValuesInRecordsDescription": "排名前 5 位的值,范围:",
"kbn.discover.fieldChooser.detailViews.visualizeLinkText": "可视化",
- "kbn.discover.fieldChooser.detailViews.warningsText": "{warningsLength, plural, one {# 个警告} other {# 个警告}}",
- "kbn.discover.fieldChooser.discoverField.addButtonLabel": "添加",
- "kbn.discover.fieldChooser.discoverField.bucketAriaLabel": "值:{value}",
- "kbn.discover.fieldChooser.discoverField.emptyStringText": "空字符串",
- "kbn.discover.fieldChooser.discoverField.removeButtonLabel": "移除",
"kbn.discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription": "脚本字段执行时间会很长。",
"kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "分析不适用于地理字段。",
"kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "分析不适用于对象字段。",