Skip to content

Commit 4cbf55c

Browse files
kertalandreadelrioMichail Yasonikcchaosflash1293
authored
[Discover] Create field_button and add popovers to sidebar (#73226) (#75389)
Co-authored-by: Andrea Del Rio <delrio.andre@gmail.com> 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>
1 parent 0ac9a5a commit 4cbf55c

19 files changed

Lines changed: 635 additions & 296 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.dscSidebarItem__fieldPopoverPanel {
2+
min-width: 260px;
3+
max-width: 300px;
4+
}

src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,4 @@ describe('discover sidebar field', function () {
104104
findTestSubject(comp, 'fieldToggle-bytes').simulate('click');
105105
expect(props.onRemoveField).toHaveBeenCalledWith('bytes');
106106
});
107-
it('should trigger onShowDetails', function () {
108-
const { comp, props } = getComponent();
109-
findTestSubject(comp, 'field-bytes-showDetails').simulate('click');
110-
expect(props.onShowDetails).toHaveBeenCalledWith(true, props.field);
111-
});
112107
});

src/plugins/discover/public/application/components/sidebar/discover_field.tsx

Lines changed: 100 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
import React from 'react';
20-
import { EuiButton } from '@elastic/eui';
19+
import React, { useState } from 'react';
20+
import { EuiPopover, EuiPopoverTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
2121
import { i18n } from '@kbn/i18n';
2222
import { DiscoverFieldDetails } from './discover_field_details';
23-
import { FieldIcon } from '../../../../../kibana_react/public';
23+
import { FieldIcon, FieldButton } from '../../../../../kibana_react/public';
2424
import { FieldDetails } from './types';
2525
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
2626
import { shortenDottedString } from '../../helpers';
2727
import { getFieldTypeName } from './lib/get_field_type_name';
28+
import './discover_field.scss';
2829

2930
export interface DiscoverFieldProps {
3031
/**
@@ -48,14 +49,6 @@ export interface DiscoverFieldProps {
4849
* @param fieldName
4950
*/
5051
onRemoveField: (fieldName: string) => void;
51-
/**
52-
* Callback to hide/show details, buckets of the field
53-
*/
54-
onShowDetails: (show: boolean, field: IndexPatternField) => void;
55-
/**
56-
* Determines, whether details of the field are displayed
57-
*/
58-
showDetails: boolean;
5952
/**
6053
* Retrieve details data for the field
6154
*/
@@ -76,22 +69,14 @@ export function DiscoverField({
7669
onAddField,
7770
onRemoveField,
7871
onAddFilter,
79-
onShowDetails,
80-
showDetails,
8172
getDetails,
8273
selected,
8374
useShortDots,
8475
}: DiscoverFieldProps) {
85-
const addLabel = i18n.translate('discover.fieldChooser.discoverField.addButtonLabel', {
86-
defaultMessage: 'Add',
87-
});
8876
const addLabelAria = i18n.translate('discover.fieldChooser.discoverField.addButtonAriaLabel', {
8977
defaultMessage: 'Add {field} to table',
9078
values: { field: field.name },
9179
});
92-
const removeLabel = i18n.translate('discover.fieldChooser.discoverField.removeButtonLabel', {
93-
defaultMessage: 'Remove',
94-
});
9580
const removeLabelAria = i18n.translate(
9681
'discover.fieldChooser.discoverField.removeButtonAriaLabel',
9782
{
@@ -100,6 +85,8 @@ export function DiscoverField({
10085
}
10186
);
10287

88+
const [infoIsOpen, setOpen] = useState(false);
89+
10390
const toggleDisplay = (f: IndexPatternField) => {
10491
if (selected) {
10592
onRemoveField(f.name);
@@ -108,78 +95,114 @@ export function DiscoverField({
10895
}
10996
};
11097

98+
function togglePopover() {
99+
setOpen(!infoIsOpen);
100+
}
101+
111102
function wrapOnDot(str?: string) {
112103
// u200B is a non-width white-space character, which allows
113104
// the browser to efficiently word-wrap right after the dot
114105
// without us having to draw a lot of extra DOM elements, etc
115106
return str ? str.replace(/\./g, '.\u200B') : '';
116107
}
117108

118-
return (
119-
<>
120-
<div
121-
className={`dscSidebarField dscSidebarItem ${showDetails ? 'dscSidebarItem--active' : ''}`}
122-
tabIndex={0}
123-
onClick={() => onShowDetails(!showDetails, field)}
124-
onKeyPress={() => onShowDetails(!showDetails, field)}
125-
data-test-subj={`field-${field.name}-showDetails`}
109+
const dscFieldIcon = (
110+
<FieldIcon type={field.type} label={getFieldTypeName(field.type)} scripted={field.scripted} />
111+
);
112+
113+
const fieldName = (
114+
<span
115+
data-test-subj={`field-${field.name}`}
116+
title={field.name}
117+
className="dscSidebarField__name"
118+
>
119+
{useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
120+
</span>
121+
);
122+
123+
let actionButton;
124+
if (field.name !== '_source' && !selected) {
125+
actionButton = (
126+
<EuiToolTip
127+
delay="long"
128+
content={i18n.translate('discover.fieldChooser.discoverField.addFieldTooltip', {
129+
defaultMessage: 'Add field as column',
130+
})}
131+
>
132+
<EuiButtonIcon
133+
iconType="plusInCircleFilled"
134+
className="dscSidebarItem__action"
135+
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
136+
ev.preventDefault();
137+
ev.stopPropagation();
138+
toggleDisplay(field);
139+
}}
140+
data-test-subj={`fieldToggle-${field.name}`}
141+
aria-label={addLabelAria}
142+
/>
143+
</EuiToolTip>
144+
);
145+
} else if (field.name !== '_source' && selected) {
146+
actionButton = (
147+
<EuiToolTip
148+
delay="long"
149+
content={i18n.translate('discover.fieldChooser.discoverField.removeFieldTooltip', {
150+
defaultMessage: 'Remove field from table',
151+
})}
126152
>
127-
<span className="dscSidebarField__fieldIcon">
128-
<FieldIcon
129-
type={field.type}
130-
label={getFieldTypeName(field.type)}
131-
scripted={field.scripted}
132-
/>
133-
</span>
134-
<span
135-
data-test-subj={`field-${field.name}`}
136-
title={field.name}
137-
className="dscSidebarField__name"
138-
>
139-
{useShortDots ? wrapOnDot(shortenDottedString(field.name)) : wrapOnDot(field.displayName)}
140-
</span>
141-
<span>
142-
{field.name !== '_source' && !selected && (
143-
<EuiButton
144-
fill
145-
size="s"
146-
className="dscSidebarItem__action"
147-
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
148-
ev.preventDefault();
149-
ev.stopPropagation();
150-
toggleDisplay(field);
151-
}}
152-
data-test-subj={`fieldToggle-${field.name}`}
153-
arial-label={addLabelAria}
154-
>
155-
{addLabel}
156-
</EuiButton>
157-
)}
158-
{field.name !== '_source' && selected && (
159-
<EuiButton
160-
color="danger"
161-
className="dscSidebarItem__action"
162-
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
163-
ev.preventDefault();
164-
ev.stopPropagation();
165-
toggleDisplay(field);
166-
}}
167-
data-test-subj={`fieldToggle-${field.name}`}
168-
arial-label={removeLabelAria}
169-
>
170-
{removeLabel}
171-
</EuiButton>
172-
)}
173-
</span>
174-
</div>
175-
{showDetails && (
153+
<EuiButtonIcon
154+
color="danger"
155+
iconType="cross"
156+
className="dscSidebarItem__action"
157+
onClick={(ev: React.MouseEvent<HTMLButtonElement>) => {
158+
ev.preventDefault();
159+
ev.stopPropagation();
160+
toggleDisplay(field);
161+
}}
162+
data-test-subj={`fieldToggle-${field.name}`}
163+
aria-label={removeLabelAria}
164+
/>
165+
</EuiToolTip>
166+
);
167+
}
168+
169+
return (
170+
<EuiPopover
171+
ownFocus
172+
display="block"
173+
button={
174+
<FieldButton
175+
size="s"
176+
className="dscSidebarItem"
177+
isActive={infoIsOpen}
178+
onClick={() => {
179+
togglePopover();
180+
}}
181+
buttonProps={{ 'data-test-subj': `field-${field.name}-showDetails` }}
182+
fieldIcon={dscFieldIcon}
183+
fieldAction={actionButton}
184+
fieldName={fieldName}
185+
/>
186+
}
187+
isOpen={infoIsOpen}
188+
closePopover={() => setOpen(false)}
189+
anchorPosition="rightUp"
190+
panelClassName="dscSidebarItem__fieldPopoverPanel"
191+
>
192+
<EuiPopoverTitle>
193+
{' '}
194+
{i18n.translate('discover.fieldChooser.discoverField.fieldTopValuesLabel', {
195+
defaultMessage: 'Top 5 values',
196+
})}
197+
</EuiPopoverTitle>
198+
{infoIsOpen && (
176199
<DiscoverFieldDetails
177200
indexPattern={indexPattern}
178201
field={field}
179202
details={getDetails(field)}
180203
onAddFilter={onAddFilter}
181204
/>
182205
)}
183-
</>
206+
</EuiPopover>
184207
);
185208
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.dscFieldDetails__visualizeBtn {
2+
@include euiFontSizeXS;
3+
height: $euiSizeL !important;
4+
min-width: $euiSize * 4;
5+
}

src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx

Lines changed: 60 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
* under the License.
1818
*/
1919
import React from 'react';
20-
import { EuiLink, EuiIconTip, EuiText } from '@elastic/eui';
20+
import { EuiLink, EuiIconTip, EuiText, EuiPopoverFooter, EuiButton, EuiSpacer } from '@elastic/eui';
2121
import { FormattedMessage } from '@kbn/i18n/react';
2222
import { DiscoverFieldBucket } from './discover_field_bucket';
2323
import { getWarnings } from './lib/get_warnings';
2424
import { Bucket, FieldDetails } from './types';
2525
import { getServices } from '../../../kibana_services';
2626
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
27+
import './discover_field_details.scss';
2728

2829
interface DiscoverFieldDetailsProps {
2930
field: IndexPatternField;
@@ -41,62 +42,68 @@ export function DiscoverFieldDetails({
4142
const warnings = getWarnings(field);
4243

4344
return (
44-
<div className="dscFieldDetails">
45-
{!details.error && (
46-
<EuiText size="xs">
47-
<FormattedMessage
48-
id="discover.fieldChooser.detailViews.topValuesInRecordsDescription"
49-
defaultMessage="Top 5 values in"
50-
/>{' '}
51-
{!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
52-
<EuiLink onClick={() => onAddFilter('_exists_', field.name, '+')}>
53-
{details.exists}
54-
</EuiLink>
55-
) : (
56-
<span>{details.exists}</span>
57-
)}{' '}
58-
/ {details.total}{' '}
59-
<FormattedMessage
60-
id="discover.fieldChooser.detailViews.recordsText"
61-
defaultMessage="records"
62-
/>
63-
</EuiText>
64-
)}
65-
{details.error && <EuiText size="xs">{details.error}</EuiText>}
66-
{!details.error && (
67-
<div style={{ marginTop: '4px' }}>
68-
{details.buckets.map((bucket: Bucket, idx: number) => (
69-
<DiscoverFieldBucket
70-
key={`bucket${idx}`}
71-
bucket={bucket}
72-
field={field}
73-
onAddFilter={onAddFilter}
74-
/>
75-
))}
76-
</div>
77-
)}
45+
<>
46+
<div className="dscFieldDetails">
47+
{details.error && <EuiText size="xs">{details.error}</EuiText>}
48+
{!details.error && (
49+
<div style={{ marginTop: '4px' }}>
50+
{details.buckets.map((bucket: Bucket, idx: number) => (
51+
<DiscoverFieldBucket
52+
key={`bucket${idx}`}
53+
bucket={bucket}
54+
field={field}
55+
onAddFilter={onAddFilter}
56+
/>
57+
))}
58+
</div>
59+
)}
7860

79-
{details.visualizeUrl && (
80-
<>
81-
<EuiLink
82-
onClick={() => {
83-
getServices().core.application.navigateToApp(details.visualizeUrl.app, {
84-
path: details.visualizeUrl.path,
85-
});
86-
}}
87-
className="kuiButton kuiButton--secondary kuiButton--small kuiVerticalRhythmSmall"
88-
data-test-subj={`fieldVisualize-${field.name}`}
89-
>
90-
<FormattedMessage
91-
id="discover.fieldChooser.detailViews.visualizeLinkText"
92-
defaultMessage="Visualize"
93-
/>
61+
{details.visualizeUrl && (
62+
<>
63+
<EuiSpacer size="xs" />
64+
<EuiButton
65+
onClick={() => {
66+
getServices().core.application.navigateToApp(details.visualizeUrl.app, {
67+
path: details.visualizeUrl.path,
68+
});
69+
}}
70+
size="s"
71+
className="dscFieldDetails__visualizeBtn"
72+
data-test-subj={`fieldVisualize-${field.name}`}
73+
>
74+
<FormattedMessage
75+
id="discover.fieldChooser.detailViews.visualizeLinkText"
76+
defaultMessage="Visualize"
77+
/>
78+
</EuiButton>
9479
{warnings.length > 0 && (
9580
<EuiIconTip type="alert" color="warning" content={warnings.join(' ')} />
9681
)}
97-
</EuiLink>
98-
</>
82+
</>
83+
)}
84+
</div>
85+
{!details.error && (
86+
<EuiPopoverFooter>
87+
<EuiText size="xs" textAlign="center">
88+
{!indexPattern.metaFields.includes(field.name) && !field.scripted ? (
89+
<EuiLink onClick={() => onAddFilter('_exists_', field.name, '+')}>
90+
<FormattedMessage
91+
id="discover.fieldChooser.detailViews.existsText"
92+
defaultMessage="Exists in"
93+
/>{' '}
94+
{details.exists}
95+
</EuiLink>
96+
) : (
97+
<span>{details.exists}</span>
98+
)}{' '}
99+
/ {details.total}{' '}
100+
<FormattedMessage
101+
id="discover.fieldChooser.detailViews.recordsText"
102+
defaultMessage="records"
103+
/>
104+
</EuiText>
105+
</EuiPopoverFooter>
99106
)}
100-
</div>
107+
</>
101108
);
102109
}

0 commit comments

Comments
 (0)