Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [Discover] Create field_button and add popovers to sidebar (#73226) #75389

Merged
merged 1 commit into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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