diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx index aaf88e68684ca0..2c669c259d07e3 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx @@ -59,7 +59,6 @@ interface Props { kqlMode: KqlMode; onChangeItemsPerPage: OnChangeItemsPerPage; query: Query; - showInspect: boolean; start: number; sort: Sort; timelineTypeContext: TimelineTypeContextProps; @@ -67,171 +66,171 @@ interface Props { utilityBar?: (totalCount: number) => React.ReactNode; } -export const EventsViewer = React.memo( - ({ - browserFields, - columns, +const EventsViewerComponent: React.FC = ({ + browserFields, + columns, + dataProviders, + deletedEventIds, + end, + filters, + headerFilterGroup, + height = DEFAULT_EVENTS_VIEWER_HEIGHT, + id, + indexPattern, + isLive, + itemsPerPage, + itemsPerPageOptions, + kqlMode, + onChangeItemsPerPage, + query, + start, + sort, + timelineTypeContext, + toggleColumn, + utilityBar, +}) => { + const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; + const kibana = useKibana(); + const combinedQueries = combineQueries({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), dataProviders, - deletedEventIds, - end, - filters, - headerFilterGroup, - height = DEFAULT_EVENTS_VIEWER_HEIGHT, - id, indexPattern, - isLive, - itemsPerPage, - itemsPerPageOptions, + browserFields, + filters, + kqlQuery: query, kqlMode, - onChangeItemsPerPage, - query, - showInspect, start, - sort, - timelineTypeContext, - toggleColumn, - utilityBar, - }) => { - const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; - const kibana = useKibana(); - const combinedQueries = combineQueries({ - config: esQuery.getEsQueryConfig(kibana.services.uiSettings), - dataProviders, - indexPattern, - browserFields, - filters, - kqlQuery: query, - kqlMode, - start, - end, - isEventViewer: true, - }); - const queryFields = useMemo( - () => - union( - columnsHeader.map(c => c.id), - timelineTypeContext.queryFields ?? [] - ), - [columnsHeader, timelineTypeContext.queryFields] - ); + end, + isEventViewer: true, + }); + const queryFields = useMemo( + () => + union( + columnsHeader.map(c => c.id), + timelineTypeContext.queryFields ?? [] + ), + [columnsHeader, timelineTypeContext.queryFields] + ); - return ( - - - {({ measureRef, content: { width = 0 } }) => ( - <> - -
- + return ( + + + {({ measureRef, content: { width = 0 } }) => ( + <> + +
+ - {combinedQueries != null ? ( - - {({ - events, - getUpdatedAt, - inspect, - loading, - loadMore, - pageInfo, - refetch, - totalCount = 0, - }) => { - const totalCountMinusDeleted = - totalCount > 0 ? totalCount - deletedEventIds.length : 0; + {combinedQueries != null ? ( + + {({ + events, + getUpdatedAt, + inspect, + loading, + loadMore, + pageInfo, + refetch, + totalCount = 0, + }) => { + const totalCountMinusDeleted = + totalCount > 0 ? totalCount - deletedEventIds.length : 0; - // TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt) - return ( - <> - - {headerFilterGroup} - + // TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt) + return ( + <> + + {headerFilterGroup} + - {utilityBar?.(totalCountMinusDeleted)} + {utilityBar?.(totalCountMinusDeleted)} -
+ - - + refetch={refetch} + /> + + !deletedEventIds.includes(e._id))} + id={id} + isEventViewer={true} + height={height} + sort={sort} + toggleColumn={toggleColumn} + /> - !deletedEventIds.includes(e._id))} - id={id} - isEventViewer={true} - height={height} - sort={sort} - toggleColumn={toggleColumn} - /> +
+ +
+ + ); + }} +
+ ) : null} + + )} + + + ); +}; -
- -
- - ); - }} - - ) : null} - - )} -
-
- ); - }, +export const EventsViewer = React.memo( + EventsViewerComponent, (prevProps, nextProps) => prevProps.browserFields === nextProps.browserFields && prevProps.columns === nextProps.columns && @@ -247,10 +246,8 @@ export const EventsViewer = React.memo( prevProps.itemsPerPageOptions === nextProps.itemsPerPageOptions && prevProps.kqlMode === nextProps.kqlMode && isEqual(prevProps.query, nextProps.query) && - prevProps.showInspect === nextProps.showInspect && prevProps.start === nextProps.start && prevProps.sort === nextProps.sort && isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) && prevProps.utilityBar === nextProps.utilityBar ); -EventsViewer.displayName = 'EventsViewer'; diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx index 1e225dabb25410..ec8d329f1dfe3e 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx @@ -57,7 +57,8 @@ describe('StatefulEventsViewer', () => { ).toBe(true); }); - test('it renders a transparent inspect button when it does NOT have mouse focus', async () => { + // InspectButtonContainer controls displaying InspectButton components + test('it renders InspectButtonContainer', async () => { const wrapper = mount( @@ -74,39 +75,6 @@ describe('StatefulEventsViewer', () => { await wait(); wrapper.update(); - expect( - wrapper - .find(`[data-test-subj="transparent-inspect-container"]`) - .first() - .exists() - ).toBe(true); - }); - - test('it renders an opaque inspect button when it has mouse focus', async () => { - const wrapper = mount( - - - - - - ); - - await wait(); - wrapper.update(); - - wrapper.simulate('mouseenter'); - wrapper.update(); - - expect( - wrapper - .find(`[data-test-subj="opaque-inspect-container"]`) - .first() - .exists() - ).toBe(true); + expect(wrapper.find(`InspectButtonContainer`).exists()).toBe(true); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx index 9b8ec243d5f387..99d174d74f3f8f 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx @@ -5,7 +5,7 @@ */ import { isEqual } from 'lodash/fp'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; import { inputsModel, inputsSelectors, State, timelineSelectors } from '../../store'; @@ -23,6 +23,7 @@ import { InputsModelId } from '../../store/inputs/constants'; import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/fetch_index_patterns'; import { TimelineTypeContextProps } from '../timeline/timeline_context'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; +import { InspectButtonContainer } from '../inspect'; import * as i18n from './translations'; export interface OwnProps { @@ -83,133 +84,102 @@ interface DispatchProps { type Props = OwnProps & StateReduxProps & DispatchProps; -const StatefulEventsViewerComponent = React.memo( - ({ - createTimeline, - columns, - dataProviders, - defaultModel, - deletedEventIds, - defaultIndices, - deleteEventQuery, - end, - filters, - headerFilterGroup, - id, - isLive, - itemsPerPage, - itemsPerPageOptions, - kqlMode, - pageFilters = [], - query, - removeColumn, - start, - showCheckboxes, - showRowRenderers, - sort, - timelineTypeContext = { - loadingText: i18n.LOADING_EVENTS, - }, - updateItemsPerPage, - upsertColumn, - utilityBar, - }) => { - const [showInspect, setShowInspect] = useState(false); - const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( - defaultIndices ?? useUiSetting(DEFAULT_INDEX_KEY) - ); - - useEffect(() => { - if (createTimeline != null) { - createTimeline({ id, columns, sort, itemsPerPage, showCheckboxes, showRowRenderers }); - } - return () => { - deleteEventQuery({ id, inputId: 'global' }); - }; - }, []); - - const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback( - itemsChangedPerPage => updateItemsPerPage({ id, itemsPerPage: itemsChangedPerPage }), - [id, updateItemsPerPage] - ); - - const toggleColumn = useCallback( - (column: ColumnHeader) => { - const exists = columns.findIndex(c => c.id === column.id) !== -1; - - if (!exists && upsertColumn != null) { - upsertColumn({ - column, - id, - index: 1, - }); - } - - if (exists && removeColumn != null) { - removeColumn({ - columnId: column.id, - id, - }); - } - }, - [columns, id, upsertColumn, removeColumn] - ); - - const handleOnMouseEnter = useCallback(() => setShowInspect(true), []); - const handleOnMouseLeave = useCallback(() => setShowInspect(false), []); - - return ( -
- -
- ); +const StatefulEventsViewerComponent: React.FC = ({ + createTimeline, + columns, + dataProviders, + deletedEventIds, + defaultIndices, + deleteEventQuery, + end, + filters, + headerFilterGroup, + id, + isLive, + itemsPerPage, + itemsPerPageOptions, + kqlMode, + pageFilters = [], + query, + removeColumn, + start, + showCheckboxes, + showRowRenderers, + sort, + timelineTypeContext = { + loadingText: i18n.LOADING_EVENTS, }, - (prevProps, nextProps) => - prevProps.id === nextProps.id && - isEqual(prevProps.columns, nextProps.columns) && - isEqual(prevProps.dataProviders, nextProps.dataProviders) && - prevProps.deletedEventIds === nextProps.deletedEventIds && - prevProps.end === nextProps.end && - isEqual(prevProps.filters, nextProps.filters) && - prevProps.isLive === nextProps.isLive && - prevProps.itemsPerPage === nextProps.itemsPerPage && - isEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) && - prevProps.kqlMode === nextProps.kqlMode && - isEqual(prevProps.query, nextProps.query) && - prevProps.pageCount === nextProps.pageCount && - isEqual(prevProps.sort, nextProps.sort) && - prevProps.start === nextProps.start && - isEqual(prevProps.pageFilters, nextProps.pageFilters) && - prevProps.showCheckboxes === nextProps.showCheckboxes && - prevProps.showRowRenderers === nextProps.showRowRenderers && - prevProps.start === nextProps.start && - isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) && - prevProps.utilityBar === nextProps.utilityBar -); + updateItemsPerPage, + upsertColumn, + utilityBar, +}) => { + const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( + defaultIndices ?? useUiSetting(DEFAULT_INDEX_KEY) + ); + + useEffect(() => { + if (createTimeline != null) { + createTimeline({ id, columns, sort, itemsPerPage, showCheckboxes, showRowRenderers }); + } + return () => { + deleteEventQuery({ id, inputId: 'global' }); + }; + }, []); + + const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback( + itemsChangedPerPage => updateItemsPerPage({ id, itemsPerPage: itemsChangedPerPage }), + [id, updateItemsPerPage] + ); + + const toggleColumn = useCallback( + (column: ColumnHeader) => { + const exists = columns.findIndex(c => c.id === column.id) !== -1; + + if (!exists && upsertColumn != null) { + upsertColumn({ + column, + id, + index: 1, + }); + } -StatefulEventsViewerComponent.displayName = 'StatefulEventsViewerComponent'; + if (exists && removeColumn != null) { + removeColumn({ + columnId: column.id, + id, + }); + } + }, + [columns, id, upsertColumn, removeColumn] + ); + + return ( + + + + ); +}; const makeMapStateToProps = () => { const getInputsTimeline = inputsSelectors.getTimelineSelector(); @@ -256,4 +226,29 @@ export const StatefulEventsViewer = connect(makeMapStateToProps, { updateItemsPerPage: timelineActions.updateItemsPerPage, removeColumn: timelineActions.removeColumn, upsertColumn: timelineActions.upsertColumn, -})(StatefulEventsViewerComponent); +})( + React.memo( + StatefulEventsViewerComponent, + (prevProps, nextProps) => + prevProps.id === nextProps.id && + isEqual(prevProps.columns, nextProps.columns) && + isEqual(prevProps.dataProviders, nextProps.dataProviders) && + prevProps.deletedEventIds === nextProps.deletedEventIds && + prevProps.end === nextProps.end && + isEqual(prevProps.filters, nextProps.filters) && + prevProps.isLive === nextProps.isLive && + prevProps.itemsPerPage === nextProps.itemsPerPage && + isEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) && + prevProps.kqlMode === nextProps.kqlMode && + isEqual(prevProps.query, nextProps.query) && + prevProps.pageCount === nextProps.pageCount && + isEqual(prevProps.sort, nextProps.sort) && + prevProps.start === nextProps.start && + isEqual(prevProps.pageFilters, nextProps.pageFilters) && + prevProps.showCheckboxes === nextProps.showCheckboxes && + prevProps.showRowRenderers === nextProps.showRowRenderers && + prevProps.start === nextProps.start && + isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) && + prevProps.utilityBar === nextProps.utilityBar + ) +); diff --git a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx index 2bc80be20e42da..bc4692b6fe0c58 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx @@ -63,36 +63,6 @@ describe('HeaderSection', () => { ).toBe(false); }); - test('it renders a transparent inspect button when showInspect is false', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper - .find('[data-test-subj="transparent-inspect-container"]') - .first() - .exists() - ).toBe(true); - }); - - test('it renders an opaque inspect button when showInspect is true', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper - .find('[data-test-subj="opaque-inspect-container"]') - .first() - .exists() - ).toBe(true); - }); - test('it renders supplements when children provided', () => { const wrapper = mount( diff --git a/x-pack/legacy/plugins/siem/public/components/header_section/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_section/index.tsx index 14af10eb6cd9bf..3153e785a8a32e 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_section/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_section/index.tsx @@ -35,48 +35,54 @@ export interface HeaderSectionProps extends HeaderProps { id?: string; split?: boolean; subtitle?: string | React.ReactNode; - showInspect?: boolean; title: string | React.ReactNode; tooltip?: string; } -export const HeaderSection = React.memo( - ({ border, children, id, showInspect = false, split, subtitle, title, tooltip }) => ( -
- - - - - -

- {title} - {tooltip && ( - <> - {' '} - - - )} -

-
+const HeaderSectionComponent: React.FC = ({ + border, + children, + id, + split, + subtitle, + title, + tooltip, +}) => ( +
+ + + + + +

+ {title} + {tooltip && ( + <> + {' '} + + + )} +

+
- {subtitle && } + {subtitle && } +
+ + {id && ( + + + )} +
+
- {id && ( - - - - )} -
+ {children && ( + + {children} - - {children && ( - - {children} - - )} - -
- ) + )} +
+
); -HeaderSection.displayName = 'HeaderSection'; + +export const HeaderSection = React.memo(HeaderSectionComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx index 26c5f499717e9c..9492002717e2bf 100644 --- a/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx @@ -17,7 +17,7 @@ import { import { createStore, State } from '../../store'; import { UpdateQueryParams, upsertQuery } from '../../store/inputs/helpers'; -import { InspectButton } from '.'; +import { InspectButton, InspectButtonContainer, BUTTON_CLASS } from '.'; import { cloneDeep } from 'lodash/fp'; describe('Inspect Button', () => { @@ -44,7 +44,7 @@ describe('Inspect Button', () => { test('Eui Empty Button', () => { const wrapper = mount( - + ); expect( @@ -58,13 +58,7 @@ describe('Inspect Button', () => { test('it does NOT render the Eui Empty Button when timeline is timeline and compact is true', () => { const wrapper = mount( - + ); expect( @@ -78,7 +72,7 @@ describe('Inspect Button', () => { test('Eui Icon Button', () => { const wrapper = mount( - + ); expect( @@ -92,13 +86,7 @@ describe('Inspect Button', () => { test('renders the Icon Button when inputId does NOT equal global, but compact is true', () => { const wrapper = mount( - + ); expect( @@ -112,7 +100,7 @@ describe('Inspect Button', () => { test('Eui Empty Button disabled', () => { const wrapper = mount( - + ); expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true); @@ -121,11 +109,41 @@ describe('Inspect Button', () => { test('Eui Icon Button disabled', () => { const wrapper = mount( - + ); expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true); }); + + describe('InspectButtonContainer', () => { + test('it renders a transparent inspect button by default', async () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find(`InspectButtonContainer`)).toHaveStyleRule('opacity', '0', { + modifier: `.${BUTTON_CLASS}`, + }); + }); + + test('it renders an opaque inspect button when it has mouse focus', async () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find(`InspectButtonContainer`)).toHaveStyleRule('opacity', '1', { + modifier: `:hover .${BUTTON_CLASS}`, + }); + }); + }); }); describe('Modal Inspect - happy path', () => { @@ -143,7 +161,7 @@ describe('Inspect Button', () => { const wrapper = mount( - + ); @@ -167,7 +185,7 @@ describe('Inspect Button', () => { const wrapper = mount( - + ); @@ -197,7 +215,7 @@ describe('Inspect Button', () => { test('Do not Open Inspect Modal if it is loading', () => { const wrapper = mount( - + ); store.getState().inputs.global.queries[0].loading = true; diff --git a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx index a2a0ffdde34a50..32e71b3db575f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx @@ -9,7 +9,7 @@ import { getOr } from 'lodash/fp'; import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import { inputsModel, inputsSelectors, State } from '../../store'; import { InputsModelId } from '../../store/inputs/constants'; @@ -18,14 +18,31 @@ import { inputsActions } from '../../store/inputs'; import { ModalInspectQuery } from './modal'; import * as i18n from './translations'; -const InspectContainer = styled.div<{ showInspect: boolean }>` - .euiButtonIcon { - ${props => (props.showInspect ? 'opacity: 1;' : 'opacity: 0;')} +export const BUTTON_CLASS = 'inspectButtonComponent'; + +export const InspectButtonContainer = styled.div<{ show?: boolean }>` + display: flex; + flex-grow: 1; + + .${BUTTON_CLASS} { + opacity: 0; transition: opacity ${props => getOr(250, 'theme.eui.euiAnimSpeedNormal', props)} ease; } + + ${({ show }) => + show && + css` + &:hover .${BUTTON_CLASS} { + opacity: 1; + } + `} `; -InspectContainer.displayName = 'InspectContainer'; +InspectButtonContainer.displayName = 'InspectButtonContainer'; + +InspectButtonContainer.defaultProps = { + show: true, +}; interface OwnProps { compact?: boolean; @@ -34,7 +51,6 @@ interface OwnProps { inspectIndex?: number; isDisabled?: boolean; onCloseInspect?: () => void; - show: boolean; title: string | React.ReactElement | React.ReactNode; } @@ -57,89 +73,84 @@ interface InspectButtonDispatch { type InspectButtonProps = OwnProps & InspectButtonReducer & InspectButtonDispatch; -const InspectButtonComponent = React.memo( - ({ - compact = false, - inputId = 'global', - inspect, - isDisabled, - isInspected, - loading, - inspectIndex = 0, - onCloseInspect, - queryId = '', - selectedInspectIndex, - setIsInspected, - show, - title = '', - }: InspectButtonProps) => { - const handleClick = useCallback(() => { - setIsInspected({ - id: queryId, - inputId, - isInspected: true, - selectedInspectIndex: inspectIndex, - }); - }, [setIsInspected, queryId, inputId, inspectIndex]); - - const handleCloseModal = useCallback(() => { - if (onCloseInspect != null) { - onCloseInspect(); - } - setIsInspected({ - id: queryId, - inputId, - isInspected: false, - selectedInspectIndex: inspectIndex, - }); - }, [onCloseInspect, setIsInspected, queryId, inputId, inspectIndex]); - - return ( - - {inputId === 'timeline' && !compact && ( - - {i18n.INSPECT} - - )} - {(inputId === 'global' || compact) && ( - - )} - 0 ? inspect.dsl[inspectIndex] : null} - response={ - inspect != null && inspect.response.length > 0 ? inspect.response[inspectIndex] : null - } - title={title} - data-test-subj="inspect-modal" - /> - - ); - } -); +const InspectButtonComponent: React.FC = ({ + compact = false, + inputId = 'global', + inspect, + isDisabled, + isInspected, + loading, + inspectIndex = 0, + onCloseInspect, + queryId = '', + selectedInspectIndex, + setIsInspected, + title = '', +}) => { + const isShowingModal = !loading && selectedInspectIndex === inspectIndex && isInspected; + const handleClick = useCallback(() => { + setIsInspected({ + id: queryId, + inputId, + isInspected: true, + selectedInspectIndex: inspectIndex, + }); + }, [setIsInspected, queryId, inputId, inspectIndex]); -InspectButtonComponent.displayName = 'InspectButtonComponent'; + const handleCloseModal = useCallback(() => { + if (onCloseInspect != null) { + onCloseInspect(); + } + setIsInspected({ + id: queryId, + inputId, + isInspected: false, + selectedInspectIndex: inspectIndex, + }); + }, [onCloseInspect, setIsInspected, queryId, inputId, inspectIndex]); + + return ( + <> + {inputId === 'timeline' && !compact && ( + + {i18n.INSPECT} + + )} + {(inputId === 'global' || compact) && ( + + )} + 0 ? inspect.dsl[inspectIndex] : null} + response={ + inspect != null && inspect.response.length > 0 ? inspect.response[inspectIndex] : null + } + title={title} + data-test-subj="inspect-modal" + /> + + ); +}; const makeMapStateToProps = () => { const getGlobalQuery = inputsSelectors.globalQueryByIdSelector(); @@ -150,6 +161,11 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export const InspectButton = connect(makeMapStateToProps, { +const mapDispatchToProps = { setIsInspected: inputsActions.setInspectionParameter, -})(InspectButtonComponent); +}; + +export const InspectButton = connect( + makeMapStateToProps, + mapDispatchToProps +)(React.memo(InspectButtonComponent)); diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx index c29b5282e13af2..94c05d00d5462d 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import { ScaleType } from '@elastic/charts'; import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; @@ -17,10 +17,11 @@ import { DEFAULT_DARK_MODE } from '../../../common/constants'; import { useUiSetting$ } from '../../lib/kibana'; import { Loader } from '../loader'; import { Panel } from '../panel'; +import { InspectButtonContainer } from '../inspect'; import { getBarchartConfigs, getCustomChartData } from './utils'; import { MatrixHistogramProps, MatrixHistogramDataTypes } from './types'; -export const MatrixHistogram = ({ +export const MatrixHistogramComponent: React.FC> = ({ data, dataKey, endDate, @@ -35,7 +36,7 @@ export const MatrixHistogram = ({ updateDateRange, yTickFormatter, showLegend, -}: MatrixHistogramProps) => { +}) => { const barchartConfigs = getBarchartConfigs({ from: startDate, to: endDate, @@ -44,7 +45,6 @@ export const MatrixHistogram = ({ yTickFormatter, showLegend, }); - const [showInspect, setShowInspect] = useState(false); const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); const [loadingInitial, setLoadingInitial] = useState(false); @@ -56,40 +56,31 @@ export const MatrixHistogram = ({ } }, [loading, loadingInitial, totalCount]); - const handleOnMouseEnter = useCallback(() => setShowInspect(true), []); - const handleOnMouseLeave = useCallback(() => setShowInspect(false), []); - return ( - - + + + - {loadingInitial ? ( - - ) : ( - <> - + {loadingInitial ? ( + + ) : ( + <> + - {loading && ( - - )} - - )} - + {loading && ( + + )} + + )} + + ); }; + +export const MatrixHistogram = React.memo(MatrixHistogramComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.tsx index 9e3f8f91d5cf78..bf32a33af1eacf 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.tsx @@ -8,14 +8,14 @@ import { EuiFlexItem } from '@elastic/eui'; import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; import { getOr } from 'lodash/fp'; -import React, { useContext, useState, useCallback } from 'react'; +import React, { useContext } from 'react'; import { DEFAULT_DARK_MODE } from '../../../../../common/constants'; import { DescriptionList } from '../../../../../common/utility_types'; import { useUiSetting$ } from '../../../../lib/kibana'; import { getEmptyTagValue } from '../../../empty_value'; import { DefaultFieldRenderer, hostIdRenderer } from '../../../field_renderers/field_renderers'; -import { InspectButton } from '../../../inspect'; +import { InspectButton, InspectButtonContainer } from '../../../inspect'; import { HostItem } from '../../../../graphql/types'; import { Loader } from '../../../loader'; import { IPDetailsLink } from '../../../links'; @@ -56,7 +56,6 @@ export const HostOverview = React.memo( anomaliesData, narrowDateRange, }) => { - const [showInspect, setShowInspect] = useState(false); const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); @@ -165,32 +164,26 @@ export const HostOverview = React.memo( ], ]; - const handleOnMouseEnter = useCallback(() => setShowInspect(true), []); - const handleOnMouseLeave = useCallback(() => setShowInspect(false), []); - return ( - - + + + - {descriptionLists.map((descriptionList, index) => - getDescriptionList(descriptionList, index) - )} + {descriptionLists.map((descriptionList, index) => + getDescriptionList(descriptionList, index) + )} - {loading && ( - - )} - + {loading && ( + + )} + + ); } ); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx index 0c4e5943995176..901b82210a6618 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx @@ -7,7 +7,7 @@ import { EuiFlexItem } from '@elastic/eui'; import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; -import React, { useContext, useState, useCallback } from 'react'; +import React, { useContext } from 'react'; import { DEFAULT_DARK_MODE } from '../../../../../common/constants'; import { DescriptionList } from '../../../../../common/utility_types'; @@ -32,7 +32,7 @@ import { Anomalies, NarrowDateRange } from '../../../ml/types'; import { AnomalyScores } from '../../../ml/score/anomaly_scores'; import { MlCapabilitiesContext } from '../../../ml/permissions/ml_capabilities_provider'; import { hasMlUserPermissions } from '../../../ml/permissions/has_ml_user_permissions'; -import { InspectButton } from '../../../inspect'; +import { InspectButton, InspectButtonContainer } from '../../../inspect'; interface OwnProps { data: IpOverviewData; @@ -71,7 +71,6 @@ export const IpOverview = React.memo( anomaliesData, narrowDateRange, }) => { - const [showInspect, setShowInspect] = useState(false); const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); @@ -140,32 +139,26 @@ export const IpOverview = React.memo( ], ]; - const handleOnMouseEnter = useCallback(() => setShowInspect(true), []); - const handleOnMouseLeave = useCallback(() => setShowInspect(false), []); - return ( - - + + + - {descriptionLists.map((descriptionList, index) => - getDescriptionList(descriptionList, index) - )} + {descriptionLists.map((descriptionList, index) => + getDescriptionList(descriptionList, index) + )} - {loading && ( - - )} - + {loading && ( + + )} + + ); } ); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx index 302917c3de93e3..a70d9d00802718 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx @@ -6,7 +6,7 @@ import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useState, useCallback } from 'react'; +import React from 'react'; import { HeaderSection } from '../../../header_section'; import { manageQuery } from '../../../page/manage_query'; @@ -17,6 +17,7 @@ import { import { inputsModel } from '../../../../store/inputs'; import { OverviewHostStats } from '../overview_host_stats'; import { getHostsUrl } from '../../../link_to'; +import { InspectButtonContainer } from '../../../inspect'; export interface OwnProps { startDate: number; @@ -36,18 +37,14 @@ export interface OwnProps { const OverviewHostStatsManage = manageQuery(OverviewHostStats); type OverviewHostProps = OwnProps; -export const OverviewHost = React.memo(({ endDate, startDate, setQuery }) => { - const [isHover, setIsHover] = useState(false); - const handleMouseEnter = useCallback(() => setIsHover(true), [setIsHover]); - const handleMouseLeave = useCallback(() => setIsHover(false), [setIsHover]); - return ( - - +const OverviewHostComponent: React.FC = ({ endDate, startDate, setQuery }) => ( + + + (({ endDate, startDate, )} - - ); -}); + + +); -OverviewHost.displayName = 'OverviewHost'; +export const OverviewHost = React.memo(OverviewHostComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx index f60957cf7b4851..af8c87ff385968 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network/index.tsx @@ -6,7 +6,7 @@ import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useState, useCallback } from 'react'; +import React from 'react'; import { HeaderSection } from '../../../header_section'; import { manageQuery } from '../../../page/manage_query'; @@ -17,6 +17,7 @@ import { import { inputsModel } from '../../../../store/inputs'; import { OverviewNetworkStats } from '../overview_network_stats'; import { getNetworkUrl } from '../../../link_to'; +import { InspectButtonContainer } from '../../../inspect'; export interface OwnProps { startDate: number; @@ -36,18 +37,13 @@ export interface OwnProps { const OverviewNetworkStatsManage = manageQuery(OverviewNetworkStats); -export const OverviewNetwork = React.memo(({ endDate, startDate, setQuery }) => { - const [isHover, setIsHover] = useState(false); - const handleMouseEnter = useCallback(() => setIsHover(true), [setIsHover]); - const handleMouseLeave = useCallback(() => setIsHover(false), [setIsHover]); - - return ( - - +const OverviewNetworkComponent: React.FC = ({ endDate, startDate, setQuery }) => ( + + + (({ endDate, startDate, setQu )} - - ); -}); + + +); -OverviewNetwork.displayName = 'OverviewNetwork'; +export const OverviewNetwork = React.memo(OverviewNetworkComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap index 9c3bf7b11e3edb..98c9fc202dd6b1 100644 --- a/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap @@ -520,7 +520,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta } } > - { width?: string; } -export const PaginatedTable = memo( - ({ - activePage, - columns, - dataTestSubj = DEFAULT_DATA_TEST_SUBJ, - headerCount, - headerSupplement, - headerTitle, - headerTooltip, - headerUnit, - id, - isInspect, - itemsPerRow, - limit, - loading, - loadPage, - onChange = noop, - pageOfItems, - showMorePagesIndicator, - sorting = null, - totalCount, - updateActivePage, - updateLimitPagination, - }) => { - const [myLoading, setMyLoading] = useState(loading); - const [myActivePage, setActivePage] = useState(activePage); - const [showInspect, setShowInspect] = useState(false); - const [loadingInitial, setLoadingInitial] = useState(headerCount === -1); - const [isPopoverOpen, setPopoverOpen] = useState(false); - - const pageCount = Math.ceil(totalCount / limit); - const dispatchToaster = useStateToaster()[1]; - - useEffect(() => { - setActivePage(activePage); - }, [activePage]); - - useEffect(() => { - if (headerCount >= 0 && loadingInitial) { - setLoadingInitial(false); - } - }, [loadingInitial, headerCount]); - - useEffect(() => { - setMyLoading(loading); - }, [loading]); - - const onButtonClick = () => { - setPopoverOpen(!isPopoverOpen); - }; - - const closePopover = () => { - setPopoverOpen(false); - }; - - const goToPage = (newActivePage: number) => { - if ((newActivePage + 1) * limit >= DEFAULT_MAX_TABLE_QUERY_SIZE) { - const toast: Toast = { - id: 'PaginationWarningMsg', - title: headerTitle + i18n.TOAST_TITLE, - color: 'warning', - iconType: 'alert', - toastLifeTimeMs: 10000, - text: i18n.TOAST_TEXT, - }; - return dispatchToaster({ - type: 'addToaster', - toast, - }); - } - setActivePage(newActivePage); - loadPage(newActivePage); - updateActivePage(newActivePage); - }; - - const button = ( - - {`${i18n.ROWS}: ${limit}`} - - ); - - const rowItems = - itemsPerRow && - itemsPerRow.map((item: ItemsPerRow) => ( - { - closePopover(); - updateLimitPagination(item.numberOfRow); - updateActivePage(0); // reset results to first page - }} - > - {item.text} - - )); - const PaginationWrapper = showMorePagesIndicator ? PaginationEuiFlexItem : EuiFlexItem; - const handleOnMouseEnter = useCallback(() => setShowInspect(true), []); - const handleOnMouseLeave = useCallback(() => setShowInspect(false), []); - - return ( - = ({ + activePage, + columns, + dataTestSubj = DEFAULT_DATA_TEST_SUBJ, + headerCount, + headerSupplement, + headerTitle, + headerTooltip, + headerUnit, + id, + isInspect, + itemsPerRow, + limit, + loading, + loadPage, + onChange = noop, + pageOfItems, + showMorePagesIndicator, + sorting = null, + totalCount, + updateActivePage, + updateLimitPagination, +}) => { + const [myLoading, setMyLoading] = useState(loading); + const [myActivePage, setActivePage] = useState(activePage); + const [loadingInitial, setLoadingInitial] = useState(headerCount === -1); + const [isPopoverOpen, setPopoverOpen] = useState(false); + + const pageCount = Math.ceil(totalCount / limit); + const dispatchToaster = useStateToaster()[1]; + + useEffect(() => { + setActivePage(activePage); + }, [activePage]); + + useEffect(() => { + if (headerCount >= 0 && loadingInitial) { + setLoadingInitial(false); + } + }, [loadingInitial, headerCount]); + + useEffect(() => { + setMyLoading(loading); + }, [loading]); + + const onButtonClick = () => { + setPopoverOpen(!isPopoverOpen); + }; + + const closePopover = () => { + setPopoverOpen(false); + }; + + const goToPage = (newActivePage: number) => { + if ((newActivePage + 1) * limit >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + const toast: Toast = { + id: 'PaginationWarningMsg', + title: headerTitle + i18n.TOAST_TITLE, + color: 'warning', + iconType: 'alert', + toastLifeTimeMs: 10000, + text: i18n.TOAST_TEXT, + }; + return dispatchToaster({ + type: 'addToaster', + toast, + }); + } + setActivePage(newActivePage); + loadPage(newActivePage); + updateActivePage(newActivePage); + }; + + const button = ( + + {`${i18n.ROWS}: ${limit}`} + + ); + + const rowItems = + itemsPerRow && + itemsPerRow.map((item: ItemsPerRow) => ( + { + closePopover(); + updateLimitPagination(item.numberOfRow); + updateActivePage(0); // reset results to first page + }} > + {item.text} + + )); + const PaginationWrapper = showMorePagesIndicator ? PaginationEuiFlexItem : EuiFlexItem; + + return ( + + = 0 ? headerCount.toLocaleString() : 0} ${headerUnit}` @@ -306,11 +298,11 @@ export const PaginatedTable = memo( )} - ); - } -); + + ); +}; -PaginatedTable.displayName = 'PaginatedTable'; +export const PaginatedTable = memo(PaginatedTableComponent); type BasicTableType = ComponentType>; // eslint-disable-line @typescript-eslint/no-explicit-any const BasicTable: typeof EuiBasicTable & { displayName: string } = styled( diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap index 5ed750b519cbfb..ca06c484dc8a29 100644 --- a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap @@ -45,71 +45,63 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] = className="euiFlexItem sc-AykKG krmHWP" data-test-subj="stat-item" > -
- +
- -
- -
- HOSTS -
-
-
-
-
- +
+ +
+ HOSTS +
+
+
+ + - - -
-
-
-
+ + +
+
- -
- - -
- - + +
-
-
- - +
-
- - + +
-

- - + +

- — - - - -

- - -
-
- + + + — + + + +

+
+
+
+ + +
+
-
-
- - + + +
+
+ +
+
-
- -
- +
- +
@@ -287,71 +279,63 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] = className="euiFlexItem sc-AykKG krmHWP" data-test-subj="stat-item" > -
- +
- -
- -
- HOSTS -
-
-
-
-
- +
+ +
+ HOSTS +
+
+
+ + - - -
-
-
-
+ + +
+
- -
-
- -
- - + +
-
-
- 0 - - +
-
- - + +
-

- - + +

- — - - - -

- - -
-
- + + + — + + + +

+
+
+
+ + +
+
-
-
- - + + +
+
+ +
+
-
- -
- +
- +
@@ -599,71 +583,63 @@ exports[`Stat Items Component rendering kpis with charts it renders the default className="euiFlexItem sc-AykKG krmHWP" data-test-subj="stat-item" > -
- +
- -
- -
- UNIQUE_PRIVATE_IPS -
-
-
-
-
- +
+ +
+ UNIQUE_PRIVATE_IPS +
+
+
+ + - - -
-
-
-
+ + +
+
- -
-
- -
- - + +
-
-
- - -
- - - + - - -
-
-
- - -
- - + + + +
+
+
+ + +
-

- 1,714 - - Source -

- - -
-
-
+ + +

+ 1,714 + + Source +

+
+
+
+ + +
+
-
-
- - - - -
+ + -
- - -
- - - + - - -
-
-
- - -
- - + + + +
+
+
+ + +
-

- 2,359 - - Dest. -

- - -
-
-
+ + +

+ 2,359 + + Dest. +

+
+
+
+ +
+
+
- - - - - - - -
-
- -
- - + +
+
+ +
+
+ +
-
- + +
+ +
+ +
+ + + +
- -
-
-
- - -
- + -
- -
- - + } + } + > +
+ +
+ + +
+
- +
- +
diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/index.tsx b/x-pack/legacy/plugins/siem/public/components/stat_items/index.tsx index 2d081468599a2a..a5c883ebd0e059 100644 --- a/x-pack/legacy/plugins/siem/public/components/stat_items/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/stat_items/index.tsx @@ -21,7 +21,7 @@ import { IconType, } from '@elastic/eui'; import { get, getOr } from 'lodash/fp'; -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import { KpiHostsData, KpiNetworkData } from '../../graphql/types'; @@ -30,7 +30,7 @@ import { BarChart } from '../charts/barchart'; import { ChartSeriesData, ChartData, ChartSeriesConfigs, UpdateDateRange } from '../charts/common'; import { getEmptyTagValue } from '../empty_value'; -import { InspectButton } from '../inspect'; +import { InspectButton, InspectButtonContainer } from '../inspect'; const FlexItem = styled(EuiFlexItem)` min-width: 0; @@ -209,9 +209,6 @@ export const StatItemsComponent = React.memo( statKey = 'item', to, }) => { - const [isHover, setIsHover] = useState(false); - const handleMouseEnter = useCallback(() => setIsHover(true), [setIsHover]); - const handleMouseLeave = useCallback(() => setIsHover(false), [setIsHover]); const isBarChartDataAvailable = barChart && barChart.length && @@ -223,72 +220,69 @@ export const StatItemsComponent = React.memo( return ( - - - - -
{description}
-
-
- - - -
+ + + + + +
{description}
+
+
+ + + +
- - {fields.map(field => ( - - - {(isAreaChartDataAvailable || isBarChartDataAvailable) && field.icon && ( - - - - )} + + {fields.map(field => ( + + + {(isAreaChartDataAvailable || isBarChartDataAvailable) && field.icon && ( + + + + )} - - -

- {field.value != null ? field.value.toLocaleString() : getEmptyTagValue()}{' '} - {field.description} -

-
-
-
-
- ))} -
+ + +

+ {field.value != null ? field.value.toLocaleString() : getEmptyTagValue()}{' '} + {field.description} +

+
+
+
+
+ ))} +
- {(enableAreaChart || enableBarChart) && } - - {enableBarChart && ( - - - - )} + {(enableAreaChart || enableBarChart) && } + + {enableBarChart && ( + + + + )} - {enableAreaChart && from != null && to != null && ( - - - - )} - -
+ {enableAreaChart && from != null && to != null && ( + + + + )} +
+
+ ); } diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx index 4027682282dcd3..b21ab5063441e8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx @@ -17,7 +17,7 @@ import { import { NewTimeline, Description, NotesButton } from './helpers'; import { OpenTimelineModalButton } from '../../open_timeline/open_timeline_modal/open_timeline_modal_button'; import { OpenTimelineModal } from '../../open_timeline/open_timeline_modal'; -import { InspectButton } from '../../inspect'; +import { InspectButton, InspectButtonContainer } from '../../inspect'; import * as i18n from './translations'; import { AssociateNote } from '../../notes/helpers'; @@ -82,32 +82,32 @@ interface Props { updateNote: UpdateNote; } -export const PropertiesRight = React.memo( - ({ - onButtonClick, - showActions, - onClosePopover, - createTimeline, - timelineId, - isDataInTimeline, - showNotesFromWidth, - showNotes, - showDescription, - showUsersView, - usersViewing, - description, - updateDescription, - associateNote, - getNotesByIds, - noteIds, - onToggleShowNotes, - updateNote, - showTimelineModal, - onCloseTimelineModal, - onOpenTimelineModal, - }) => ( - - +const PropertiesRightComponent: React.FC = ({ + onButtonClick, + showActions, + onClosePopover, + createTimeline, + timelineId, + isDataInTimeline, + showNotesFromWidth, + showNotes, + showDescription, + showUsersView, + usersViewing, + description, + updateDescription, + associateNote, + getNotesByIds, + noteIds, + onToggleShowNotes, + updateNote, + showTimelineModal, + onCloseTimelineModal, + onOpenTimelineModal, +}) => ( + + + ( inspectIndex={0} isDisabled={!isDataInTimeline} onCloseInspect={onClosePopover} - show={true} title={i18n.INSPECT_TIMELINE_TITLE} /> @@ -177,26 +176,26 @@ export const PropertiesRight = React.memo( ) : null} - - - {showUsersView - ? usersViewing.map(user => ( - // Hide the hard-coded elastic user avatar as the 7.2 release does not implement - // support for multi-user-collaboration as proposed in elastic/ingest-dev#395 - - - - - - )) - : null} - - {showTimelineModal ? : null} - - ) + + + + {showUsersView + ? usersViewing.map(user => ( + // Hide the hard-coded elastic user avatar as the 7.2 release does not implement + // support for multi-user-collaboration as proposed in elastic/ingest-dev#395 + + + + + + )) + : null} + + {showTimelineModal ? : null} + ); -PropertiesRight.displayName = 'PropertiesRight'; +export const PropertiesRight = React.memo(PropertiesRightComponent);