[Security Solution] Fix tooltip for timeline sourcerer (#115950)

* fix tooltip for event picker

* remove unused mock

* remove unused dependency

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Angela Chuang 2021-10-25 11:16:13 +01:00 committed by GitHub
parent ed99e2466a
commit 780c43513a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 215 additions and 114 deletions

View file

@ -7,6 +7,9 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { waitFor } from '@testing-library/react';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
import { SourcererScopeName } from '../../store/sourcerer/model'; import { SourcererScopeName } from '../../store/sourcerer/model';
import { Sourcerer } from './index'; import { Sourcerer } from './index';
import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants';
@ -19,8 +22,6 @@ import {
TestProviders, TestProviders,
} from '../../mock'; } from '../../mock';
import { createStore, State } from '../../store'; import { createStore, State } from '../../store';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
import { waitFor } from '@testing-library/react';
const mockDispatch = jest.fn(); const mockDispatch = jest.fn();
jest.mock('react-redux', () => { jest.mock('react-redux', () => {
@ -46,6 +47,7 @@ const mockOptions = [
const defaultProps = { const defaultProps = {
scope: sourcererModel.SourcererScopeName.default, scope: sourcererModel.SourcererScopeName.default,
}; };
describe('Sourcerer component', () => { describe('Sourcerer component', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -59,6 +61,34 @@ describe('Sourcerer component', () => {
store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
}); });
it('renders tooltip', () => {
const wrapper = mount(
<TestProviders>
<Sourcerer {...defaultProps} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="sourcerer-tooltip"]').prop('content')).toEqual(
mockOptions
.map((p) => p.label)
.sort()
.join(', ')
);
});
it('renders popover button inside tooltip', () => {
const wrapper = mount(
<TestProviders>
<Sourcerer {...defaultProps} />
</TestProviders>
);
expect(
wrapper
.find('[data-test-subj="sourcerer-tooltip"] [data-test-subj="sourcerer-trigger"]')
.exists()
).toBeTruthy();
});
// Using props callback instead of simulating clicks, // Using props callback instead of simulating clicks,
// because EuiSelectable uses a virtualized list, which isn't easily testable via test subjects // because EuiSelectable uses a virtualized list, which isn't easily testable via test subjects
it('Mounts with all options selected', () => { it('Mounts with all options selected', () => {

View file

@ -169,55 +169,62 @@ export const Sourcerer = React.memo<SourcererComponentProps>(({ scope: scopeId }
[isPopoverOpen, sourcererScope.selectedPatterns] [isPopoverOpen, sourcererScope.selectedPatterns]
); );
const buttonWithTooptip = useMemo(() => {
return tooltipContent ? (
<EuiToolTip position="top" content={tooltipContent} data-test-subj="sourcerer-tooltip">
{trigger}
</EuiToolTip>
) : (
trigger
);
}, [trigger, tooltipContent]);
return ( return (
<EuiToolTip position="top" content={tooltipContent}> <EuiPopover
<EuiPopover data-test-subj="sourcerer-popover"
data-test-subj="sourcerer-popover" button={buttonWithTooptip}
button={trigger} isOpen={isPopoverOpen}
isOpen={isPopoverOpen} closePopover={handleClosePopOver}
closePopover={handleClosePopOver} panelPaddingSize="s"
display="block" repositionOnScroll
panelPaddingSize="s" ownFocus
repositionOnScroll >
ownFocus <PopoverContent>
> <EuiPopoverTitle>
<PopoverContent> <>{i18n.SELECT_INDEX_PATTERNS}</>
<EuiPopoverTitle> </EuiPopoverTitle>
<>{i18n.SELECT_INDEX_PATTERNS}</> <EuiSpacer size="s" />
</EuiPopoverTitle> <EuiText color="default">{i18n.INDEX_PATTERNS_SELECTION_LABEL}</EuiText>
<EuiSpacer size="s" /> <EuiSpacer size="xs" />
<EuiText color="default">{i18n.INDEX_PATTERNS_SELECTION_LABEL}</EuiText> {comboBox}
<EuiSpacer size="xs" /> <EuiSpacer size="s" />
{comboBox} <EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiSpacer size="s" /> <EuiFlexItem>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween"> <ResetButton
<EuiFlexItem> aria-label={i18n.INDEX_PATTERNS_RESET}
<ResetButton data-test-subj="sourcerer-reset"
aria-label={i18n.INDEX_PATTERNS_RESET} flush="left"
data-test-subj="sourcerer-reset" onClick={resetDataSources}
flush="left" title={i18n.INDEX_PATTERNS_RESET}
onClick={resetDataSources} >
title={i18n.INDEX_PATTERNS_RESET} {i18n.INDEX_PATTERNS_RESET}
> </ResetButton>
{i18n.INDEX_PATTERNS_RESET} </EuiFlexItem>
</ResetButton> <EuiFlexItem grow={false}>
</EuiFlexItem> <EuiButton
<EuiFlexItem grow={false}> onClick={handleSaveIndices}
<EuiButton disabled={isSavingDisabled}
onClick={handleSaveIndices} data-test-subj="add-index"
disabled={isSavingDisabled} fill
data-test-subj="add-index" fullWidth
fill size="s"
fullWidth >
size="s" {i18n.SAVE_INDEX_PATTERNS}
> </EuiButton>
{i18n.SAVE_INDEX_PATTERNS} </EuiFlexItem>
</EuiButton> </EuiFlexGroup>
</EuiFlexItem> </PopoverContent>
</EuiFlexGroup> </EuiPopover>
</PopoverContent>
</EuiPopover>
</EuiToolTip>
); );
}); });
Sourcerer.displayName = 'Sourcerer'; Sourcerer.displayName = 'Sourcerer';

View file

@ -5,7 +5,9 @@
* 2.0. * 2.0.
*/ */
import { fireEvent, render } from '@testing-library/react'; import { fireEvent, render, within } from '@testing-library/react';
import { EuiToolTip } from '@elastic/eui';
import React from 'react'; import React from 'react';
import { PickEventType } from './pick_events'; import { PickEventType } from './pick_events';
import { import {
@ -19,6 +21,14 @@ import { TimelineEventsType } from '../../../../../common';
import { createStore } from '../../../../common/store'; import { createStore } from '../../../../common/store';
import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
jest.mock('@elastic/eui', () => {
const actual = jest.requireActual('@elastic/eui');
return {
...actual,
EuiToolTip: jest.fn(),
};
});
describe('pick_events', () => { describe('pick_events', () => {
const defaultProps = { const defaultProps = {
eventType: 'all' as TimelineEventsType, eventType: 'all' as TimelineEventsType,
@ -53,6 +63,23 @@ describe('pick_events', () => {
}, },
}; };
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const mockTooltip = ({
tooltipContent,
children,
}: {
tooltipContent: string;
children: React.ReactElement;
}) => (
<div data-test-subj="timeline-sourcerer-tooltip">
<span>{tooltipContent}</span>
{children}
</div>
);
beforeAll(() => {
(EuiToolTip as unknown as jest.Mock).mockImplementation(mockTooltip);
});
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
jest.restoreAllMocks(); jest.restoreAllMocks();
@ -68,6 +95,32 @@ describe('pick_events', () => {
initialPatterns.sort().join('') initialPatterns.sort().join('')
); );
}); });
it('renders tooltip', () => {
render(
<TestProviders>
<PickEventType {...defaultProps} />
</TestProviders>
);
expect((EuiToolTip as unknown as jest.Mock).mock.calls[0][0].content).toEqual(
initialPatterns
.filter((p) => p != null)
.sort()
.join(', ')
);
});
it('renders popover button inside tooltip', () => {
const wrapper = render(
<TestProviders>
<PickEventType {...defaultProps} />
</TestProviders>
);
const tooltip = wrapper.getByTestId('timeline-sourcerer-tooltip');
expect(within(tooltip).getByTestId('sourcerer-timeline-trigger')).toBeTruthy();
});
it('correctly filters options', () => { it('correctly filters options', () => {
const wrapper = render( const wrapper = render(
<TestProviders store={store}> <TestProviders store={store}>

View file

@ -295,6 +295,20 @@ const PickEventTypeComponents: React.FC<PickEventTypeProps> = ({
[isPopoverOpen, sourcererScope.selectedPatterns] [isPopoverOpen, sourcererScope.selectedPatterns]
); );
const buttonWithTooptip = useMemo(() => {
return tooltipContent ? (
<EuiToolTip
position="top"
content={tooltipContent}
data-test-subj="timeline-sourcerer-tooltip"
>
{button}
</EuiToolTip>
) : (
button
);
}, [button, tooltipContent]);
const ButtonContent = useMemo( const ButtonContent = useMemo(
() => ( () => (
<AdvancedSettings data-test-subj="advanced-settings"> <AdvancedSettings data-test-subj="advanced-settings">
@ -326,69 +340,66 @@ const PickEventTypeComponents: React.FC<PickEventTypeProps> = ({
return ( return (
<PickEventContainer> <PickEventContainer>
<EuiToolTip position="top" content={tooltipContent}> <EuiPopover
<EuiPopover button={buttonWithTooptip}
button={button} closePopover={closePopover}
closePopover={closePopover} id="popover"
id="popover" isOpen={isPopoverOpen}
isOpen={isPopoverOpen} repositionOnScroll
ownFocus >
repositionOnScroll <PopoverContent>
> <EuiPopoverTitle>
<PopoverContent> <>{i18n.SELECT_INDEX_PATTERNS}</>
<EuiPopoverTitle> </EuiPopoverTitle>
<>{i18n.SELECT_INDEX_PATTERNS}</> <EuiSpacer size="s" />
</EuiPopoverTitle> {filter}
<EuiSpacer size="s" /> <EuiSpacer size="m" />
{filter} <EuiAccordion
<EuiSpacer size="m" /> id="accordion1"
<EuiAccordion forceState={showAdvanceSettings ? 'open' : 'closed'}
id="accordion1" buttonContent={ButtonContent}
forceState={showAdvanceSettings ? 'open' : 'closed'} onToggle={setAdvanceSettings}
buttonContent={ButtonContent} >
onToggle={setAdvanceSettings} <>
> <EuiSpacer size="s" />
<> {comboBox}
<EuiSpacer size="s" /> </>
{comboBox} </EuiAccordion>
</> {!showAdvanceSettings && (
</EuiAccordion> <>
{!showAdvanceSettings && ( <EuiSpacer size="s" />
<> <ConfigHelper size="s" color="subdued">
<EuiSpacer size="s" /> {i18n.CONFIGURE_INDEX_PATTERNS}
<ConfigHelper size="s" color="subdued"> </ConfigHelper>
{i18n.CONFIGURE_INDEX_PATTERNS} </>
</ConfigHelper> )}
</> <EuiSpacer size="m" />
)} <EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiSpacer size="m" /> <EuiFlexItem>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween"> <ResetButton
<EuiFlexItem> aria-label={i18n.DATA_SOURCES_RESET}
<ResetButton data-test-subj="sourcerer-reset"
aria-label={i18n.DATA_SOURCES_RESET} flush="left"
data-test-subj="sourcerer-reset" onClick={resetDataSources}
flush="left" title={i18n.DATA_SOURCES_RESET}
onClick={resetDataSources} >
title={i18n.DATA_SOURCES_RESET} {i18n.DATA_SOURCES_RESET}
> </ResetButton>
{i18n.DATA_SOURCES_RESET} </EuiFlexItem>
</ResetButton> <EuiFlexItem grow={false}>
</EuiFlexItem> <EuiButton
<EuiFlexItem grow={false}> onClick={handleSaveIndices}
<EuiButton data-test-subj="add-index"
onClick={handleSaveIndices} fill
data-test-subj="add-index" fullWidth
fill size="s"
fullWidth >
size="s" {i18n.SAVE_INDEX_PATTERNS}
> </EuiButton>
{i18n.SAVE_INDEX_PATTERNS} </EuiFlexItem>
</EuiButton> </EuiFlexGroup>
</EuiFlexItem> </PopoverContent>
</EuiFlexGroup> </EuiPopover>
</PopoverContent>
</EuiPopover>
</EuiToolTip>
</PickEventContainer> </PickEventContainer>
); );
}; };