## [SIEM] Disables Timeline Provider Mutations While Loading Data (#38185) (#38232)

## Summary

* Fix: disables dropping a new data provider provider on the timeline, deleting an existing data provider, or choosing an action from the data provider context menu while the timeline is loading data to prevent draggable errors, like the one pictured in the screenshot below:

![draggable-error](https://user-images.githubusercontent.com/4459398/58996789-ebb7ce00-87b6-11e9-9490-47a0df6f89ab.png)

* Fix: the timeline settings icon appears too close to the `Refresh` button:
### Before (Chrome)

![timeline-gear-before](https://user-images.githubusercontent.com/4459398/58997030-e6a74e80-87b7-11e9-86df-cb67592e63a7.png)

### After (Chrome)

<img width="1679" alt="timeline-gear-after-chrome" src="https://user-images.githubusercontent.com/4459398/58997064-1ce4ce00-87b8-11e9-9ffb-0f412e0018ad.png">

### After (Firefox)

<img width="1672" alt="timeline-gear-after-firefox" src="https://user-images.githubusercontent.com/4459398/58997113-4f8ec680-87b8-11e9-9b52-dc8a13d51361.png">

### After (Safari)

<img width="1680" alt="timeline-gear-after-safari" src="https://user-images.githubusercontent.com/4459398/58997169-911f7180-87b8-11e9-8186-0ff31e41ad53.png">

### After (IE 11)

<img width="1354" alt="timeline-gear-after-ie11" src="https://user-images.githubusercontent.com/4459398/58997306-40f4df00-87b9-11e9-844d-b12c7e16f7cb.png">

* Fix: the `Open Timeline` modal is showing bulk-delete actions

### Before (Chrome)

![open-timeline-before](https://user-images.githubusercontent.com/4459398/58997349-700b5080-87b9-11e9-8299-7dbeccbd8ff9.png)

### After (Chrome)

<img width="1008" alt="open-timeline-after" src="https://user-images.githubusercontent.com/4459398/58997381-9af5a480-87b9-11e9-9909-2d68bd5ff2c3.png">

* Fix: action icons on the `Timelines` page and `Open Timelines` modal are not aligned with the counts

### Before (Chrome)

<img width="1552" alt="all-timelines-before" src="https://user-images.githubusercontent.com/4459398/58997554-659d8680-87ba-11e9-868c-d92ad72f44b8.png">

### After (Chrome)

<img width="1550" alt="all-timelines-after" src="https://user-images.githubusercontent.com/4459398/58997656-d8a6fd00-87ba-11e9-9038-c0ca464f8c4d.png">

* Fix: Changed the title text of the fields browser from `Select Fields` to `Customize Columns`

### Before (Chrome)

<img width="910" alt="fields-browser-before" src="https://user-images.githubusercontent.com/4459398/58997616-a4334100-87ba-11e9-9cd8-02ed9d265bf2.png">

### After (Chrome)

<img width="910" alt="fields-browser-after" src="https://user-images.githubusercontent.com/4459398/58997691-fa07e900-87ba-11e9-8cd9-6412d2cef8f0.png">

https://github.com/elastic/ingest-dev/issues/471
This commit is contained in:
Andrew Goldstein 2019-06-06 02:19:59 -06:00 committed by GitHub
parent e20d5b88ce
commit 5746d01ae0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 378 additions and 72 deletions

View file

@ -91,7 +91,7 @@ const TitleRow = pure<{ onOutsideClick: () => void; onUpdateColumns: OnUpdateCol
>
<EuiFlexItem grow={false}>
<EuiTitle size="s">
<h2>{i18n.SELECT_FIELDS}</h2>
<h2>{i18n.CUSTOMIZE_COLUMNS}</h2>
</EuiTitle>
</EuiFlexItem>

View file

@ -6,7 +6,7 @@
import { ActionCreator } from 'typescript-fsa';
import { connect } from 'react-redux';
import { EuiButton } from '@elastic/eui';
import { EuiButton, EuiToolTip } from '@elastic/eui';
import { noop } from 'lodash/fp';
import * as React from 'react';
import styled from 'styled-components';
@ -116,16 +116,18 @@ export class StatefulFieldsBrowserComponent extends React.PureComponent<
return (
<>
<FieldsBrowserButtonContainer>
<EuiButton
color="primary"
data-test-subj="show-field-browser"
iconSide="right"
iconType="arrowDown"
onClick={this.toggleShow}
size="s"
>
{i18n.FIELDS}
</EuiButton>
<EuiToolTip content={i18n.CUSTOMIZE_COLUMNS}>
<EuiButton
color="primary"
data-test-subj="show-field-browser"
iconSide="right"
iconType="arrowDown"
onClick={this.toggleShow}
size="s"
>
{i18n.FIELDS}
</EuiButton>
</EuiToolTip>
</FieldsBrowserButtonContainer>
{show && (

View file

@ -24,6 +24,10 @@ export const COPY_TO_CLIPBOARD = i18n.translate('xpack.siem.fieldBrowser.copyToC
defaultMessage: 'Copy to Clipboard',
});
export const CUSTOMIZE_COLUMNS = i18n.translate('xpack.siem.fieldBrowser.customizeColumnsTitle', {
defaultMessage: 'Customize Columns',
});
export const DESCRIPTION = i18n.translate('xpack.siem.fieldBrowser.descriptionLabel', {
defaultMessage: 'Description',
});
@ -62,10 +66,6 @@ export const RESET_FIELDS = i18n.translate('xpack.siem.fieldBrowser.resetFieldsL
defaultMessage: 'Reset Fields',
});
export const SELECT_FIELDS = i18n.translate('xpack.siem.fieldBrowser.selectFieldsTitle', {
defaultMessage: 'Select Fields',
});
export const VIEW_CATEGORY = (categoryId: string) =>
i18n.translate('xpack.siem.fieldBrowser.viewCategoryTooltip', {
defaultMessage: 'View all {categoryId} fields',

View file

@ -185,7 +185,6 @@ export class StatefulOpenTimelineComponent extends React.PureComponent<
isLoading={loading}
itemIdToExpandedNotesRowMap={itemIdToExpandedNotesRowMap}
onAddTimelinesToFavorites={undefined}
onDeleteSelected={this.onDeleteSelected}
onlyFavorites={onlyFavorites}
onOpenTimeline={this.openTimeline}
onQueryChange={this.onQueryChange}

View file

@ -6,6 +6,7 @@
import { EuiIcon, EuiToolTip } from '@elastic/eui';
import * as React from 'react';
import styled from 'styled-components';
import { ACTION_COLUMN_WIDTH, PositionedIcon } from './common_styles';
import { FavoriteTimelineResult, OpenTimelineResult } from '../types';
@ -13,16 +14,25 @@ import { getNotesCount, getPinnedEventCount } from '../helpers';
import * as i18n from '../translations';
const PinnedIcon = styled(EuiIcon)`
position: relative;
left: -3px;
`;
const CommentIcon = styled(EuiIcon)`
position: relative;
left: -2px;
`;
/**
* Returns the columns that have icon headers
*/
export const getIconHeaderColumns = () => [
{
align: 'center',
field: 'pinnedEventIds',
name: (
<EuiToolTip content={i18n.PINNED_EVENTS}>
<EuiIcon data-test-subj="pinned-event-header-icon" size="m" color="subdued" type="pin" />
<PinnedIcon data-test-subj="pinned-event-header-icon" size="m" color="subdued" type="pin" />
</EuiToolTip>
),
render: (_: Record<string, boolean> | null | undefined, timelineResult: OpenTimelineResult) => (
@ -32,11 +42,10 @@ export const getIconHeaderColumns = () => [
width: ACTION_COLUMN_WIDTH,
},
{
align: 'center',
field: 'eventIdToNoteIds',
name: (
<EuiToolTip content={i18n.NOTES}>
<EuiIcon
<CommentIcon
data-test-subj="notes-count-header-icon"
size="m"
color="subdued"
@ -52,7 +61,6 @@ export const getIconHeaderColumns = () => [
width: ACTION_COLUMN_WIDTH,
},
{
align: 'center',
field: 'favorite',
name: (
<EuiToolTip content={i18n.FAVORITES}>

View file

@ -19,6 +19,7 @@ import {
OnToggleDataProviderEnabled,
OnToggleDataProviderExcluded,
} from '../events';
import { TimelineContext } from '../timeline_context';
import { DataProvider } from './data_provider';
import { Empty } from './empty';
@ -83,23 +84,27 @@ export const DataProviders = pure<Props>(
show,
}) => (
<DropTargetDataProviders data-test-subj="dataProviders">
<DroppableWrapper isDropDisabled={!show} droppableId={getDroppableId(id)}>
{dataProviders != null && dataProviders.length ? (
<Providers
browserFields={browserFields}
id={id}
dataProviders={dataProviders}
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
onDataProviderEdited={onDataProviderEdited}
onDataProviderRemoved={onDataProviderRemoved}
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
/>
) : (
<Empty />
<TimelineContext.Consumer>
{({ isLoading }) => (
<DroppableWrapper isDropDisabled={!show || isLoading} droppableId={getDroppableId(id)}>
{dataProviders != null && dataProviders.length ? (
<Providers
browserFields={browserFields}
id={id}
dataProviders={dataProviders}
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
onDataProviderEdited={onDataProviderEdited}
onDataProviderRemoved={onDataProviderRemoved}
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
/>
) : (
<Empty />
)}
</DroppableWrapper>
)}
</DroppableWrapper>
</TimelineContext.Consumer>
</DropTargetDataProviders>
)
);

View file

@ -30,6 +30,7 @@ interface OwnProps {
kqlQuery: string;
isEnabled: boolean;
isExcluded: boolean;
isLoading: boolean;
isOpen: boolean;
onDataProviderEdited?: OnDataProviderEdited;
operator: QueryOperator;
@ -52,6 +53,7 @@ export const getProviderActions = ({
field,
isEnabled,
isExcluded,
isLoading,
operator,
onDataProviderEdited,
onFilterForFieldPresent,
@ -67,6 +69,7 @@ export const getProviderActions = ({
field: string;
isEnabled: boolean;
isExcluded: boolean;
isLoading: boolean;
onDataProviderEdited?: OnDataProviderEdited;
onFilterForFieldPresent: () => void;
operator: QueryOperator;
@ -81,30 +84,35 @@ export const getProviderActions = ({
items: [
{
className: EDIT_CLASS_NAME,
disabled: isLoading,
icon: 'pencil',
name: i18n.EDIT_MENU_ITEM,
panel: 1,
},
{
className: EXCLUDE_CLASS_NAME,
disabled: isLoading,
icon: `${isExcluded ? 'plusInCircle' : 'minusInCircle'}`,
name: isExcluded ? i18n.INCLUDE_DATA_PROVIDER : i18n.EXCLUDE_DATA_PROVIDER,
onClick: toggleExcluded,
},
{
className: ENABLE_CLASS_NAME,
disabled: isLoading,
icon: `${isEnabled ? 'eyeClosed' : 'eye'}`,
name: isEnabled ? i18n.TEMPORARILY_DISABLE_DATA_PROVIDER : i18n.RE_ENABLE_DATA_PROVIDER,
onClick: toggleEnabled,
},
{
className: FILTER_FOR_FIELD_PRESENT_CLASS_NAME,
disabled: isLoading,
icon: 'logstashFilter',
name: i18n.FILTER_FOR_FIELD_PRESENT,
onClick: onFilterForFieldPresent,
},
{
className: DELETE_CLASS_NAME,
disabled: isLoading,
icon: 'trash',
name: i18n.DELETE_DATA_PROVIDER,
onClick: deleteItem,
@ -143,6 +151,7 @@ export class ProviderItemActions extends React.PureComponent<OwnProps> {
field,
isEnabled,
isExcluded,
isLoading,
isOpen,
operator,
providerId,
@ -159,6 +168,7 @@ export class ProviderItemActions extends React.PureComponent<OwnProps> {
field,
isEnabled,
isExcluded,
isLoading,
onDataProviderEdited: this.onDataProviderEdited,
onFilterForFieldPresent: this.onFilterForFieldPresent,
operator,

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { noop } from 'lodash/fp';
import React, { PureComponent } from 'react';
import { BrowserFields } from '../../../containers/source';
@ -12,6 +13,7 @@ import { OnDataProviderEdited } from '../events';
import { ProviderBadge } from './provider_badge';
import { ProviderItemActions } from './provider_item_actions';
import { QueryOperator } from './data_provider';
import { TimelineContext } from '../timeline_context';
interface ProviderItemBadgeProps {
andProviderId?: string;
@ -55,40 +57,43 @@ export class ProviderItemBadge extends PureComponent<ProviderItemBadgeProps, Own
val,
} = this.props;
const badge = (
<ProviderBadge
deleteProvider={deleteProvider}
field={field}
kqlQuery={kqlQuery}
isEnabled={isEnabled}
isExcluded={isExcluded}
providerId={providerId}
togglePopover={this.togglePopover}
val={val}
operator={operator}
/>
);
return (
<ProviderItemActions
andProviderId={andProviderId}
browserFields={browserFields}
button={badge}
closePopover={this.closePopover}
deleteProvider={deleteProvider}
field={field}
kqlQuery={kqlQuery}
isEnabled={isEnabled}
isExcluded={isExcluded}
isOpen={this.state.isPopoverOpen}
onDataProviderEdited={onDataProviderEdited}
operator={operator}
providerId={providerId}
timelineId={timelineId}
toggleEnabledProvider={this.toggleEnabledProvider}
toggleExcludedProvider={this.toggleExcludedProvider}
value={val}
/>
<TimelineContext.Consumer>
{({ isLoading }) => (
<ProviderItemActions
andProviderId={andProviderId}
browserFields={browserFields}
button={
<ProviderBadge
deleteProvider={!isLoading ? deleteProvider : noop}
field={field}
kqlQuery={kqlQuery}
isEnabled={isEnabled}
isExcluded={isExcluded}
providerId={providerId}
togglePopover={this.togglePopover}
val={val}
operator={operator}
/>
}
closePopover={this.closePopover}
deleteProvider={deleteProvider}
field={field}
kqlQuery={kqlQuery}
isEnabled={isEnabled}
isExcluded={isExcluded}
isLoading={isLoading}
isOpen={this.state.isPopoverOpen}
onDataProviderEdited={onDataProviderEdited}
operator={operator}
providerId={providerId}
timelineId={timelineId}
toggleEnabledProvider={this.toggleEnabledProvider}
toggleExcludedProvider={this.toggleExcludedProvider}
value={val}
/>
)}
</TimelineContext.Consumer>
);
}

View file

@ -10,6 +10,7 @@ import * as React from 'react';
import { TestProviders } from '../../../mock/test_providers';
import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper';
import { TimelineContext } from '../timeline_context';
import { mockDataProviders } from './mock/mock_data_providers';
import { getDraggableId, Providers } from './providers';
@ -90,6 +91,36 @@ describe('Providers', () => {
expect(mockOnDataProviderRemoved.mock.calls[0][0]).toEqual('id-Provider 1');
});
test('while loading data, it does NOT invoke the onDataProviderRemoved callback when the close button is clicked', () => {
const mockOnDataProviderRemoved = jest.fn();
const wrapper = mount(
<TestProviders>
<TimelineContext.Provider value={{ isLoading: true }}>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
id="foo"
onChangeDataProviderKqlQuery={jest.fn()}
onChangeDroppableAndProvider={jest.fn()}
onDataProviderEdited={jest.fn()}
onDataProviderRemoved={mockOnDataProviderRemoved}
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
/>
</DroppableWrapper>
</TimelineContext.Provider>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"] svg')
.first()
.simulate('click');
expect(mockOnDataProviderRemoved).not.toBeCalled();
});
test('it invokes the onDataProviderRemoved callback when you click on the option "Delete" in the provider menu', () => {
const mockOnDataProviderRemoved = jest.fn();
const wrapper = mount(
@ -113,13 +144,51 @@ describe('Providers', () => {
.find('[data-test-subj="providerBadge"]')
.first()
.simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${DELETE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnDataProviderRemoved.mock.calls[0][0]).toEqual('id-Provider 1');
});
test('while loading data, it does NOT invoke the onDataProviderRemoved callback when you click on the option "Delete" in the provider menu', () => {
const mockOnDataProviderRemoved = jest.fn();
const wrapper = mount(
<TestProviders>
<TimelineContext.Provider value={{ isLoading: true }}>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
id="foo"
onChangeDataProviderKqlQuery={jest.fn()}
onChangeDroppableAndProvider={jest.fn()}
onDataProviderEdited={jest.fn()}
onDataProviderRemoved={mockOnDataProviderRemoved}
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
/>
</DroppableWrapper>
</TimelineContext.Provider>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.first()
.simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${DELETE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnDataProviderRemoved).not.toBeCalled();
});
});
describe('#getDraggableId', () => {
@ -150,6 +219,7 @@ describe('Providers', () => {
</DroppableWrapper>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.first()
@ -165,6 +235,42 @@ describe('Providers', () => {
providerId: 'id-Provider 1',
});
});
test('while loading data, it does NOT invoke the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
const mockOnToggleDataProviderEnabled = jest.fn();
const wrapper = mount(
<TestProviders>
<TimelineContext.Provider value={{ isLoading: true }}>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
id="foo"
onChangeDataProviderKqlQuery={jest.fn()}
onChangeDroppableAndProvider={jest.fn()}
onDataProviderEdited={jest.fn()}
onDataProviderRemoved={jest.fn()}
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
onToggleDataProviderExcluded={jest.fn()}
/>
</DroppableWrapper>
</TimelineContext.Provider>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.first()
.simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${ENABLE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnToggleDataProviderEnabled).not.toBeCalled();
});
});
describe('#onToggleDataProviderExcluded', () => {
@ -206,6 +312,44 @@ describe('Providers', () => {
providerId: 'id-Provider 1',
});
});
test('while loading data, it does NOT invoke the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
const onToggleDataProviderExcluded = jest.fn();
const wrapper = mount(
<TestProviders>
<TimelineContext.Provider value={{ isLoading: true }}>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
id="foo"
onChangeDataProviderKqlQuery={jest.fn()}
onChangeDroppableAndProvider={jest.fn()}
onDataProviderEdited={jest.fn()}
onDataProviderRemoved={jest.fn()}
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
/>
</DroppableWrapper>
</TimelineContext.Provider>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.first()
.simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${EXCLUDE_CLASS_NAME}`)
.first()
.simulate('click');
expect(onToggleDataProviderExcluded).not.toBeCalled();
});
});
describe('#ProviderWithAndProvider', () => {
@ -276,6 +420,43 @@ describe('Providers', () => {
expect(mockOnDataProviderRemoved.mock.calls[0]).toEqual(['id-Provider 1', 'id-Provider 2']);
});
test('while loading data, it does NOT invoke the onDataProviderRemoved callback when you click on the close button is clicked', () => {
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
const mockOnDataProviderRemoved = jest.fn();
const wrapper = mount(
<TestProviders>
<TimelineContext.Provider value={{ isLoading: true }}>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={mockDataProviders}
id="foo"
onChangeDataProviderKqlQuery={jest.fn()}
onChangeDroppableAndProvider={jest.fn()}
onDataProviderEdited={jest.fn()}
onDataProviderRemoved={mockOnDataProviderRemoved}
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
/>
</DroppableWrapper>
</TimelineContext.Provider>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.at(4)
.find('svg')
.first()
.simulate('click');
wrapper.update();
expect(mockOnDataProviderRemoved).not.toBeCalled();
});
test('it invokes the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
@ -318,6 +499,46 @@ describe('Providers', () => {
});
});
test('while loading data, it does NOT invoke the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
const mockOnToggleDataProviderEnabled = jest.fn();
const wrapper = mount(
<TestProviders>
<TimelineContext.Provider value={{ isLoading: true }}>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={dataProviders}
id="foo"
onChangeDataProviderKqlQuery={jest.fn()}
onChangeDroppableAndProvider={jest.fn()}
onDataProviderEdited={jest.fn()}
onDataProviderRemoved={jest.fn()}
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
onToggleDataProviderExcluded={jest.fn()}
/>
</DroppableWrapper>
</TimelineContext.Provider>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.at(4)
.simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${ENABLE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnToggleDataProviderEnabled).not.toBeCalled();
});
test('it invokes the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
@ -359,5 +580,45 @@ describe('Providers', () => {
providerId: 'id-Provider 1',
});
});
test('while loading data, it does NOT invoke the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
const dataProviders = mockDataProviders.slice(0, 1);
dataProviders[0].and = mockDataProviders.slice(1, 3);
const mockOnToggleDataProviderExcluded = jest.fn();
const wrapper = mount(
<TestProviders>
<TimelineContext.Provider value={{ isLoading: true }}>
<DroppableWrapper droppableId="unitTest">
<Providers
browserFields={{}}
dataProviders={dataProviders}
id="foo"
onChangeDataProviderKqlQuery={jest.fn()}
onChangeDroppableAndProvider={jest.fn()}
onDataProviderEdited={jest.fn()}
onDataProviderRemoved={jest.fn()}
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={mockOnToggleDataProviderExcluded}
/>
</DroppableWrapper>
</TimelineContext.Provider>
</TestProviders>
);
wrapper
.find('[data-test-subj="providerBadge"]')
.at(4)
.simulate('click');
wrapper.update();
wrapper
.find(`[data-test-subj="providerActions"] .${EXCLUDE_CLASS_NAME}`)
.first()
.simulate('click');
expect(mockOnToggleDataProviderExcluded).not.toBeCalled();
});
});
});

View file

@ -59,6 +59,7 @@ const DescriptionPopoverMenuContainer = styled.div`
`;
const SettingsIcon = styled(EuiIcon)`
margin-left: 4px;
cursor: pointer;
`;

View file

@ -14,6 +14,7 @@ import { StaticIndexPattern } from 'ui/index_patterns';
import { BrowserFields } from '../../containers/source';
import { TimelineQuery } from '../../containers/timeline';
import { Direction } from '../../graphql/types';
import { KqlMode } from '../../store/timeline/model';
import { AutoSizer } from '../auto_sizer';
import { ColumnHeader } from './body/column_headers/column_header';
@ -34,7 +35,7 @@ import { Footer, footerHeight } from './footer';
import { TimelineHeader } from './header';
import { calculateBodyHeight, combineQueries } from './helpers';
import { TimelineRefetch } from './refetch_timeline';
import { KqlMode } from '../../store/timeline/model';
import { TimelineContext } from './timeline_context';
const WrappedByAutoSizer = styled.div`
width: 100%;
@ -150,6 +151,7 @@ export const Timeline = pure<Props>(
>
{({ events, loading, totalCount, pageInfo, loadMore, getUpdatedAt, refetch }) => (
<TimelineRefetch loading={loading} id={id} refetch={refetch}>
<TimelineContext.Provider value={{ isLoading: loading }} />
<StatefulBody
browserFields={browserFields}
data={events}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as React from 'react';
export interface TimelineContextData {
isLoading: boolean;
}
export const TimelineContext = React.createContext<TimelineContextData>({ isLoading: false });