Skip to content

Commit

Permalink
[Discover] Create field_button and add popovers to sidebar (elastic#7…
Browse files Browse the repository at this point in the history
…3226)

Co-authored-by: Michail Yasonik <michail.yasonik@elastic.co>
Co-authored-by: cchaos <caroline.horn@elastic.co>
Co-authored-by: Joe Reuter <johannes.reuter@elastic.co>
  • Loading branch information
4 people authored and kertal committed Aug 19, 2020
1 parent 93b47ab commit f2b1ec1
Show file tree
Hide file tree
Showing 19 changed files with 635 additions and 296 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.dscSidebarItem__fieldPopoverPanel {
min-width: 260px;
max-width: 300px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -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
*/
Expand All @@ -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',
{
Expand All @@ -100,6 +85,8 @@ export function DiscoverField({
}
);

const [infoIsOpen, setOpen] = useState(false);

const toggleDisplay = (f: IndexPatternField) => {
if (selected) {
onRemoveField(f.name);
Expand All @@ -108,78 +95,114 @@ 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
// without us having to draw a lot of extra DOM elements, etc
return str ? str.replace(/\./g, '.\u200B') : '';
}

return (
<>
<div
className={`dscSidebarField dscSidebarItem ${showDetails ? 'dscSidebarItem--active' : ''}`}
tabIndex={0}
onClick={() => onShowDetails(!showDetails, field)}
onKeyPress={() => onShowDetails(!showDetails, field)}
data-test-subj={`field-${field.name}-showDetails`}
const dscFieldIcon = (
<FieldIcon type={field.type} label={getFieldTypeName(field.type)} scripted={field.scripted} />
);

const fieldName = (
<span
data-test-subj={`field-${field.name}`}
title={field.name}
className="dscSidebarField__name"
>
{useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
</span>
);

let actionButton;
if (field.name !== '_source' && !selected) {
actionButton = (
<EuiToolTip
delay="long"
content={i18n.translate('discover.fieldChooser.discoverField.addFieldTooltip', {
defaultMessage: 'Add field as column',
})}
>
<EuiButtonIcon
iconType="plusInCircleFilled"
className="dscSidebarItem__action"
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
ev.stopPropagation();
toggleDisplay(field);
}}
data-test-subj={`fieldToggle-${field.name}`}
aria-label={addLabelAria}
/>
</EuiToolTip>
);
} else if (field.name !== '_source' && selected) {
actionButton = (
<EuiToolTip
delay="long"
content={i18n.translate('discover.fieldChooser.discoverField.removeFieldTooltip', {
defaultMessage: 'Remove field from table',
})}
>
<span className="dscSidebarField__fieldIcon">
<FieldIcon
type={field.type}
label={getFieldTypeName(field.type)}
scripted={field.scripted}
/>
</span>
<span
data-test-subj={`field-${field.name}`}
title={field.name}
className="dscSidebarField__name"
>
{useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
</span>
<span>
{field.name !== '_source' && !selected && (
<EuiButton
fill
size="s"
className="dscSidebarItem__action"
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
ev.stopPropagation();
toggleDisplay(field);
}}
data-test-subj={`fieldToggle-${field.name}`}
arial-label={addLabelAria}
>
{addLabel}
</EuiButton>
)}
{field.name !== '_source' && selected && (
<EuiButton
color="danger"
className="dscSidebarItem__action"
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
ev.stopPropagation();
toggleDisplay(field);
}}
data-test-subj={`fieldToggle-${field.name}`}
arial-label={removeLabelAria}
>
{removeLabel}
</EuiButton>
)}
</span>
</div>
{showDetails && (
<EuiButtonIcon
color="danger"
iconType="cross"
className="dscSidebarItem__action"
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
ev.stopPropagation();
toggleDisplay(field);
}}
data-test-subj={`fieldToggle-${field.name}`}
aria-label={removeLabelAria}
/>
</EuiToolTip>
);
}

return (
<EuiPopover
ownFocus
display="block"
button={
<FieldButton
size="s"
className="dscSidebarItem"
isActive={infoIsOpen}
onClick={() => {
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"
>
<EuiPopoverTitle>
{' '}
{i18n.translate('discover.fieldChooser.discoverField.fieldTopValuesLabel', {
defaultMessage: 'Top 5 values',
})}
</EuiPopoverTitle>
{infoIsOpen && (
<DiscoverFieldDetails
indexPattern={indexPattern}
field={field}
details={getDetails(field)}
onAddFilter={onAddFilter}
/>
)}
</>
</EuiPopover>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.dscFieldDetails__visualizeBtn {
@include euiFontSizeXS;
height: $euiSizeL !important;
min-width: $euiSize * 4;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,62 +42,68 @@ export function DiscoverFieldDetails({
const warnings = getWarnings(field);

return (
<div className="dscFieldDetails">
{!details.error && (
<EuiText size="xs">
<FormattedMessage
id="discover.fieldChooser.detailViews.topValuesInRecordsDescription"
defaultMessage="Top 5 values in"
/>{' '}
{!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
<EuiLink onClick={() => onAddFilter('_exists_', field.name, '+')}>
{details.exists}
</EuiLink>
) : (
<span>{details.exists}</span>
)}{' '}
/ {details.total}{' '}
<FormattedMessage
id="discover.fieldChooser.detailViews.recordsText"
defaultMessage="records"
/>
</EuiText>
)}
{details.error && <EuiText size="xs">{details.error}</EuiText>}
{!details.error && (
<div style={{ marginTop: '4px' }}>
{details.buckets.map((bucket: Bucket, idx: number) => (
<DiscoverFieldBucket
key={`bucket${idx}`}
bucket={bucket}
field={field}
onAddFilter={onAddFilter}
/>
))}
</div>
)}
<>
<div className="dscFieldDetails">
{details.error && <EuiText size="xs">{details.error}</EuiText>}
{!details.error && (
<div style={{ marginTop: '4px' }}>
{details.buckets.map((bucket: Bucket, idx: number) => (
<DiscoverFieldBucket
key={`bucket${idx}`}
bucket={bucket}
field={field}
onAddFilter={onAddFilter}
/>
))}
</div>
)}

{details.visualizeUrl && (
<>
<EuiLink
onClick={() => {
getServices().core.application.navigateToApp(details.visualizeUrl.app, {
path: details.visualizeUrl.path,
});
}}
className="kuiButton kuiButton--secondary kuiButton--small kuiVerticalRhythmSmall"
data-test-subj={`fieldVisualize-${field.name}`}
>
<FormattedMessage
id="discover.fieldChooser.detailViews.visualizeLinkText"
defaultMessage="Visualize"
/>
{details.visualizeUrl && (
<>
<EuiSpacer size="xs" />
<EuiButton
onClick={() => {
getServices().core.application.navigateToApp(details.visualizeUrl.app, {
path: details.visualizeUrl.path,
});
}}
size="s"
className="dscFieldDetails__visualizeBtn"
data-test-subj={`fieldVisualize-${field.name}`}
>
<FormattedMessage
id="discover.fieldChooser.detailViews.visualizeLinkText"
defaultMessage="Visualize"
/>
</EuiButton>
{warnings.length > 0 && (
<EuiIconTip type="alert" color="warning" content={warnings.join(' ')} />
)}
</EuiLink>
</>
</>
)}
</div>
{!details.error && (
<EuiPopoverFooter>
<EuiText size="xs" textAlign="center">
{!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
<EuiLink onClick={() => onAddFilter('_exists_', field.name, '+')}>
<FormattedMessage
id="discover.fieldChooser.detailViews.existsText"
defaultMessage="Exists in"
/>{' '}
{details.exists}
</EuiLink>
) : (
<span>{details.exists}</span>
)}{' '}
/ {details.total}{' '}
<FormattedMessage
id="discover.fieldChooser.detailViews.recordsText"
defaultMessage="records"
/>
</EuiText>
</EuiPopoverFooter>
)}
</div>
</>
);
}
Loading

0 comments on commit f2b1ec1

Please sign in to comment.