diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.scss b/src/plugins/discover/public/application/components/sidebar/discover_field.scss
new file mode 100644
index 0000000000000..8e1dd41f66ab1
--- /dev/null
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.scss
@@ -0,0 +1,4 @@
+.dscSidebarItem__fieldPopoverPanel {
+ min-width: 260px;
+ max-width: 300px;
+}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx
index e1abbfd7657d0..a0d9e3c541e47 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx
@@ -104,9 +104,4 @@ describe('discover sidebar field', function () {
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/plugins/discover/public/application/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
index 724908281146d..639dbfe09277c 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx
@@ -16,15 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React from 'react';
-import { EuiButton } from '@elastic/eui';
+import React, { useState } from 'react';
+import { EuiPopover, EuiPopoverTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { DiscoverFieldDetails } from './discover_field_details';
-import { FieldIcon } from '../../../../../kibana_react/public';
+import { FieldIcon, FieldButton } from '../../../../../kibana_react/public';
import { FieldDetails } from './types';
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
import { shortenDottedString } from '../../helpers';
import { getFieldTypeName } from './lib/get_field_type_name';
+import './discover_field.scss';
export interface DiscoverFieldProps {
/**
@@ -48,14 +49,6 @@ export interface DiscoverFieldProps {
* @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
*/
@@ -76,22 +69,14 @@ export function DiscoverField({
onAddField,
onRemoveField,
onAddFilter,
- onShowDetails,
- showDetails,
getDetails,
selected,
useShortDots,
}: DiscoverFieldProps) {
- const addLabel = i18n.translate('discover.fieldChooser.discoverField.addButtonLabel', {
- defaultMessage: 'Add',
- });
const addLabelAria = i18n.translate('discover.fieldChooser.discoverField.addButtonAriaLabel', {
defaultMessage: 'Add {field} to table',
values: { field: field.name },
});
- const removeLabel = i18n.translate('discover.fieldChooser.discoverField.removeButtonLabel', {
- defaultMessage: 'Remove',
- });
const removeLabelAria = i18n.translate(
'discover.fieldChooser.discoverField.removeButtonAriaLabel',
{
@@ -100,6 +85,8 @@ export function DiscoverField({
}
);
+ const [infoIsOpen, setOpen] = useState(false);
+
const toggleDisplay = (f: IndexPatternField) => {
if (selected) {
onRemoveField(f.name);
@@ -108,6 +95,10 @@ export function DiscoverField({
}
};
+ function togglePopover() {
+ setOpen(!infoIsOpen);
+ }
+
function wrapOnDot(str?: string) {
// u200B is a non-width white-space character, which allows
// the browser to efficiently word-wrap right after the dot
@@ -115,64 +106,96 @@ export function DiscoverField({
return str ? str.replace(/\./g, '.\u200B') : '';
}
- return (
- <>
-
onShowDetails(!showDetails, field)}
- onKeyPress={() => onShowDetails(!showDetails, field)}
- data-test-subj={`field-${field.name}-showDetails`}
+ const dscFieldIcon = (
+
+ );
+
+ const fieldName = (
+
+ {useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
+
+ );
+
+ let actionButton;
+ if (field.name !== '_source' && !selected) {
+ actionButton = (
+
+ ) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ toggleDisplay(field);
+ }}
+ data-test-subj={`fieldToggle-${field.name}`}
+ aria-label={addLabelAria}
+ />
+
+ );
+ } else if (field.name !== '_source' && selected) {
+ actionButton = (
+
-
-
-
-
- {useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(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 && (
+ ) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ toggleDisplay(field);
+ }}
+ data-test-subj={`fieldToggle-${field.name}`}
+ aria-label={removeLabelAria}
+ />
+
+ );
+ }
+
+ return (
+ {
+ togglePopover();
+ }}
+ buttonProps={{ 'data-test-subj': `field-${field.name}-showDetails` }}
+ fieldIcon={dscFieldIcon}
+ fieldAction={actionButton}
+ fieldName={fieldName}
+ />
+ }
+ isOpen={infoIsOpen}
+ closePopover={() => setOpen(false)}
+ anchorPosition="rightUp"
+ panelClassName="dscSidebarItem__fieldPopoverPanel"
+ >
+
+ {' '}
+ {i18n.translate('discover.fieldChooser.discoverField.fieldTopValuesLabel', {
+ defaultMessage: 'Top 5 values',
+ })}
+
+ {infoIsOpen && (
)}
- >
+
);
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.scss b/src/plugins/discover/public/application/components/sidebar/discover_field_details.scss
new file mode 100644
index 0000000000000..f4b3eed741f9f
--- /dev/null
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.scss
@@ -0,0 +1,5 @@
+.dscFieldDetails__visualizeBtn {
+ @include euiFontSizeXS;
+ height: $euiSizeL !important;
+ min-width: $euiSize * 4;
+}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
index dd95a45f71626..875b5a0aa446f 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx
@@ -17,13 +17,14 @@
* under the License.
*/
import React from 'react';
-import { EuiLink, EuiIconTip, EuiText } from '@elastic/eui';
+import { EuiLink, EuiIconTip, EuiText, EuiPopoverFooter, EuiButton, EuiSpacer } 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 { getServices } from '../../../kibana_services';
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
+import './discover_field_details.scss';
interface DiscoverFieldDetailsProps {
field: IndexPatternField;
@@ -41,62 +42,68 @@ export function DiscoverFieldDetails({
const warnings = getWarnings(field);
return (
-
- {!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 && (
-
- {details.buckets.map((bucket: Bucket, idx: number) => (
-
- ))}
-
- )}
+ <>
+
+ {details.error &&
{details.error}}
+ {!details.error && (
+
+ {details.buckets.map((bucket: Bucket, idx: number) => (
+
+ ))}
+
+ )}
- {details.visualizeUrl && (
- <>
-
{
- getServices().core.application.navigateToApp(details.visualizeUrl.app, {
- path: details.visualizeUrl.path,
- });
- }}
- className="kuiButton kuiButton--secondary kuiButton--small kuiVerticalRhythmSmall"
- data-test-subj={`fieldVisualize-${field.name}`}
- >
-
+ {details.visualizeUrl && (
+ <>
+
+ {
+ getServices().core.application.navigateToApp(details.visualizeUrl.app, {
+ path: details.visualizeUrl.path,
+ });
+ }}
+ size="s"
+ className="dscFieldDetails__visualizeBtn"
+ data-test-subj={`fieldVisualize-${field.name}`}
+ >
+
+
{warnings.length > 0 && (
)}
-
- >
+ >
+ )}
+
+ {!details.error && (
+
+
+ {!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
+ onAddFilter('_exists_', field.name, '+')}>
+ {' '}
+ {details.exists}
+
+ ) : (
+ {details.exists}
+ )}{' '}
+ / {details.total}{' '}
+
+
+
)}
-
+ >
);
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss
index 07efd64752c84..f130b0399f467 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss
@@ -42,54 +42,15 @@
}
.dscSidebarItem {
- border-top: 1px solid transparent;
- position: relative;
- display: flex;
- align-items: center;
- justify-content: space-between;
- cursor: pointer;
- font-size: $euiFontSizeXS;
- border-top: solid 1px transparent;
- border-bottom: solid 1px transparent;
- line-height: normal;
- margin-bottom: $euiSizeXS * 0.5;
-
&:hover,
- &:focus {
+ &:focus-within,
+ &[class*='-isActive'] {
.dscSidebarItem__action {
opacity: 1;
}
}
}
-.dscSidebarItem--active {
- border-top: 1px solid $euiColorLightShade;
- color: $euiColorFullShade;
-}
-
-.dscSidebarField {
- padding: $euiSizeXS;
- display: flex;
- align-items: center;
- max-width: 100%;
- width: 100%;
- border: none;
- border-radius: $euiBorderRadius - 1px;
- text-align: left;
-}
-
-.dscSidebarField__name {
- margin-left: $euiSizeS;
- flex-grow: 1;
- word-break: break-word;
- padding-right: 1px;
-}
-
-.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.
@@ -101,7 +62,7 @@
&:focus {
opacity: 1; /* 2 */
}
- font-size: 12px;
+ font-size: $euiFontSizeXS;
padding: 2px 6px !important;
height: 22px !important;
min-width: auto !important;
@@ -130,8 +91,6 @@
}
.dscFieldDetails {
- padding: $euiSizeS;
- background-color: $euiColorLightestShade;
color: $euiTextColor;
margin-bottom: $euiSizeS;
}
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
index 58b468762c501..450bb93f60bf3 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
@@ -92,7 +92,6 @@ export function DiscoverSidebar({
setIndexPattern,
state,
}: DiscoverSidebarProps) {
- const [openFieldMap, setOpenFieldMap] = useState(new Map());
const [showFields, setShowFields] = useState(false);
const [fields, setFields] = useState(null);
const [fieldFilterState, setFieldFilterState] = useState(getDefaultFieldFilter());
@@ -103,19 +102,6 @@ export function DiscoverSidebar({
setFields(newFields);
}, [selectedIndexPattern, fieldCounts, hits, services]);
- 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)));
- if (services.capabilities.discover.save) {
- selectedIndexPattern.popularizeField(field.name, 1);
- }
- }
- },
- [openFieldMap, selectedIndexPattern, services.capabilities.discover.save]
- );
const onChangeFieldSearch = useCallback(
(field: string, value: string | boolean | undefined) => {
const newState = setFieldFilterProp(fieldFilterState, field, value);
@@ -213,9 +199,7 @@ export function DiscoverSidebar({
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
- onShowDetails={onShowDetails}
getDetails={getDetailsByField}
- showDetails={openFieldMap.get(field.name) || false}
selected={true}
useShortDots={useShortDots}
/>
@@ -290,9 +274,7 @@ export function DiscoverSidebar({
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
- onShowDetails={onShowDetails}
getDetails={getDetailsByField}
- showDetails={openFieldMap.get(field.name) || false}
useShortDots={useShortDots}
/>
@@ -318,9 +300,7 @@ export function DiscoverSidebar({
onAddField={onAddField}
onRemoveField={onRemoveField}
onAddFilter={onAddFilter}
- onShowDetails={onShowDetails}
getDetails={getDetailsByField}
- showDetails={openFieldMap.get(field.name) || false}
useShortDots={useShortDots}
/>
diff --git a/src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap b/src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap
new file mode 100644
index 0000000000000..e65b5fcb8fbbd
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/__snapshots__/field_button.test.tsx.snap
@@ -0,0 +1,134 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`fieldAction is rendered 1`] = `
+
+
+
+
+ fieldAction
+
+
+
+`;
+
+exports[`fieldIcon is rendered 1`] = `
+
+
+
+`;
+
+exports[`isActive defaults to false 1`] = `
+
+
+
+`;
+
+exports[`isActive renders true 1`] = `
+
+
+
+`;
+
+exports[`isDraggable is rendered 1`] = `
+
+
+
+`;
+
+exports[`sizes m is applied 1`] = `
+
+
+
+`;
+
+exports[`sizes s is applied 1`] = `
+
+
+
+`;
diff --git a/src/plugins/kibana_react/public/field_button/field_button.scss b/src/plugins/kibana_react/public/field_button/field_button.scss
new file mode 100644
index 0000000000000..43f60e4503576
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/field_button.scss
@@ -0,0 +1,75 @@
+.kbnFieldButton {
+ @include euiFontSizeS;
+ border-radius: $euiBorderRadius;
+ margin-bottom: $euiSizeXS;
+ display: flex;
+ align-items: center;
+ transition: box-shadow $euiAnimSpeedFast $euiAnimSlightResistance,
+ background-color $euiAnimSpeedFast $euiAnimSlightResistance; // sass-lint:disable-line indentation
+
+ &:focus-within,
+ &-isActive {
+ @include euiFocusRing;
+ }
+}
+
+.kbnFieldButton--isDraggable {
+ background: lightOrDarkTheme($euiColorEmptyShade, $euiColorLightestShade);
+
+ &:hover,
+ &:focus,
+ &:focus-within {
+ @include euiBottomShadowMedium;
+ border-radius: $euiBorderRadius;
+ z-index: 2;
+ }
+
+ .kbnFieldButton__button {
+ &:hover,
+ &:focus {
+ cursor: grab;
+ }
+ }
+}
+
+.kbnFieldButton__button {
+ flex-grow: 1;
+ text-align: left;
+ padding: $euiSizeS;
+ display: flex;
+ align-items: flex-start;
+}
+
+.kbnFieldButton__fieldIcon {
+ flex-shrink: 0;
+ line-height: 0;
+ margin-right: $euiSizeS;
+}
+
+.kbnFieldButton__name {
+ flex-grow: 1;
+ word-break: break-word;
+}
+
+.kbnFieldButton__infoIcon {
+ flex-shrink: 0;
+ margin-left: $euiSizeXS;
+}
+
+.kbnFieldButton__fieldAction {
+ margin-right: $euiSizeS;
+}
+
+// Reduce text size and spacing for the small size
+.kbnFieldButton--small {
+ font-size: $euiFontSizeXS;
+
+ .kbnFieldButton__button {
+ padding: $euiSizeXS;
+ }
+
+ .kbnFieldButton__fieldIcon,
+ .kbnFieldButton__fieldAction {
+ margin-right: $euiSizeXS;
+ }
+}
diff --git a/src/plugins/kibana_react/public/field_button/field_button.test.tsx b/src/plugins/kibana_react/public/field_button/field_button.test.tsx
new file mode 100644
index 0000000000000..32e1203b89718
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/field_button.test.tsx
@@ -0,0 +1,68 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import { FieldButton, SIZES } from './field_button';
+
+const noop = () => {};
+
+describe('sizes', () => {
+ SIZES.forEach((size) => {
+ test(`${size} is applied`, () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ });
+ });
+});
+
+describe('isDraggable', () => {
+ it('is rendered', () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('fieldIcon', () => {
+ it('is rendered', () => {
+ const component = shallow(
+ fieldIcon} />
+ );
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('fieldAction', () => {
+ it('is rendered', () => {
+ const component = shallow(
+ fieldAction} />
+ );
+ expect(component).toMatchSnapshot();
+ });
+});
+
+describe('isActive', () => {
+ it('defaults to false', () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ });
+ it('renders true', () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/src/plugins/kibana_react/public/field_button/field_button.tsx b/src/plugins/kibana_react/public/field_button/field_button.tsx
new file mode 100644
index 0000000000000..e5833b261946a
--- /dev/null
+++ b/src/plugins/kibana_react/public/field_button/field_button.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 './field_button.scss';
+import classNames from 'classnames';
+import React, { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react';
+import { CommonProps } from '@elastic/eui';
+
+export interface FieldButtonProps extends HTMLAttributes {
+ /**
+ * Label for the button
+ */
+ fieldName: ReactNode;
+ /**
+ * Icon representing the field type.
+ * Recommend using FieldIcon
+ */
+ fieldIcon?: ReactNode;
+ /**
+ * An optional node to place inside and at the end of the