From 4ac37aa4dc188b5624d8d804a7d0aa3533ffb1cd Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Fri, 27 Aug 2021 16:12:50 -0400 Subject: [PATCH 1/6] Make analyzer work with EuiDataGrid full screen --- .../global_kql_header/index.tsx | 2 +- .../events_viewer/events_viewer.tsx | 4 +- .../common/components/events_viewer/index.tsx | 180 ++++++++++-------- .../containers/use_full_screen/index.tsx | 50 ++++- .../investigate_in_resolver.tsx | 16 +- .../security_solution/public/plugin.tsx | 3 + .../components/graph_overlay/index.test.tsx | 24 ++- .../components/graph_overlay/index.tsx | 143 ++++++++++---- .../timeline/graph_tab_content/index.tsx | 2 +- x-pack/plugins/timelines/public/plugin.ts | 7 + 10 files changed, 299 insertions(+), 132 deletions(-) diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx index 3e3c91133eab61..04df8d17e2d389 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx @@ -11,7 +11,7 @@ import { useGlobalHeaderPortal } from '../../../../common/hooks/use_global_heade const StyledStickyWrapper = styled.div` position: sticky; - z-index: ${(props) => props.theme.eui.euiZLevel2}; + z-index: 5; // TOP location is declared in src/public/rendering/_base.scss to keep in line with Kibana Chrome `; diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index b8b6b9766bdde3..b18e90034af10e 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -321,7 +321,9 @@ const EventsViewerComponent: React.FC = ({ refetch={refetch} /> - {graphEventId && } + {graphEventId && ( + + )} = ({ const globalFilters = useMemo(() => [...filters, ...(pageFilters ?? [])], [filters, pageFilters]); const trailingControlColumns: ControlColumnProps[] = EMPTY_CONTROL_COLUMNS; + const [isDataGridExpanded, setDataGridExpanded] = useState(false); + const onMutation = useCallback(() => { + const isExpanded = document.querySelector('.euiDataGrid--fullScreen') !== null; + setDataGridExpanded(isExpanded); + }, [setDataGridExpanded]); const graphOverlay = useMemo( () => graphEventId != null && graphEventId.length > 0 ? ( - + ) : null, - [graphEventId, id] + [graphEventId, id, isDataGridExpanded] ); + return ( - <> - - - {tGridEnabled ? ( - timelinesUi.getTGrid<'embedded'>({ - id, - type: 'embedded', - browserFields, - columns, - dataProviders: dataProviders!, - defaultCellActions, - deletedEventIds, - docValueFields, - end, - entityType, - filters: globalFilters, - globalFullScreen, - graphOverlay, - hasAlertsCrud, - indexNames: selectedPatterns, - indexPattern, - isLive, - isLoadingIndexPattern, - itemsPerPage, - itemsPerPageOptions: itemsPerPageOptions!, - kqlMode, - query, - onRuleChange, - renderCellValue, - rowRenderers, - start, - sort, - additionalFilters, - graphEventId, - filterStatus: currentFilter, - leadingControlColumns, - trailingControlColumns, - tGridEventRenderedViewEnabled, - unit, - }) - ) : ( - - )} - - - - + + {(mutationRef) => ( + <> + + + {tGridEnabled ? ( + timelinesUi.getTGrid<'embedded'>({ + id, + type: 'embedded', + browserFields, + columns, + dataProviders: dataProviders!, + defaultCellActions, + deletedEventIds, + docValueFields, + end, + entityType, + filters: globalFilters, + globalFullScreen, + graphOverlay, + hasAlertsCrud, + indexNames: selectedPatterns, + indexPattern, + isLive, + isLoadingIndexPattern, + itemsPerPage, + itemsPerPageOptions: itemsPerPageOptions!, + kqlMode, + query, + onRuleChange, + renderCellValue, + rowRenderers, + start, + sort, + additionalFilters, + graphEventId, + filterStatus: currentFilter, + leadingControlColumns, + trailingControlColumns, + tGridEventRenderedViewEnabled, + unit, + }) + ) : ( + + )} + + + + + )} + ); }; diff --git a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx index e19db8bb94b466..9099733f5db4a4 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx @@ -29,6 +29,44 @@ export const resetScroll = () => { }, 0); }; +export const closeDataGrid = () => { + const escapePress = new KeyboardEvent('click', { + view: window, + bubbles: true, + cancelable: true, + }); + const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen'); + const dataGridFullScreenButtonNew = document.querySelector( + '[data-test-subj="dataGridFullScrenButton"]' + ); + const dataGridFullScreenButtonOld = document.querySelector( + '[data-test-subj="dataGridFullScreenButton"]' + ); + const dataGridFullScreenButton = dataGridFullScreenButtonNew || dataGridFullScreenButtonOld; + if (dataGridIsFullScreen && dataGridFullScreenButton) { + dataGridFullScreenButton.dispatchEvent(escapePress); + } +}; + +export const expandDataGrid = () => { + const clickEvent = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + }); + const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen'); + const dataGridFullScreenButtonNew = document.querySelector( + '[data-test-subj="dataGridFullScrenButton"]' + ); + const dataGridFullScreenButtonOld = document.querySelector( + '[data-test-subj="dataGridFullScreenButton"]' + ); + const dataGridFullScreenButton = dataGridFullScreenButtonNew || dataGridFullScreenButtonOld; + if (dataGridFullScreenButton && !dataGridIsFullScreen) { + dataGridFullScreenButton.dispatchEvent(clickEvent); + } +}; + interface GlobalFullScreen { globalFullScreen: boolean; setGlobalFullScreen: (fullScreen: boolean) => void; @@ -46,9 +84,11 @@ export const useGlobalFullScreen = (): GlobalFullScreen => { const setGlobalFullScreen = useCallback( (fullScreen: boolean) => { if (fullScreen) { + expandDataGrid(); document.body.classList.add(SCROLLING_DISABLED_CLASS_NAME); resetScroll(); } else { + closeDataGrid(); document.body.classList.remove(SCROLLING_DISABLED_CLASS_NAME); resetScroll(); } @@ -71,9 +111,15 @@ export const useTimelineFullScreen = (): TimelineFullScreen => { const dispatch = useDispatch(); const timelineFullScreen = useShallowEqualSelector(inputsSelectors.timelineFullScreenSelector) ?? false; - const setTimelineFullScreen = useCallback( - (fullScreen: boolean) => dispatch(inputsActions.setFullScreen({ id: 'timeline', fullScreen })), + (fullScreen: boolean) => { + if (fullScreen) { + expandDataGrid(); + } else { + closeDataGrid(); + } + dispatch(inputsActions.setFullScreen({ id: 'timeline', fullScreen })); + }, [dispatch] ); const memoizedReturn = useMemo( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx index 2e23ecc648aee4..21d0e132599fbd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.tsx @@ -13,6 +13,10 @@ import { setActiveTabTimeline, updateTimelineGraphEventId, } from '../../../../timelines/store/timeline/actions'; +import { + useGlobalFullScreen, + useTimelineFullScreen, +} from '../../../../common/containers/use_full_screen'; import { TimelineId, TimelineTabs } from '../../../../../common'; import { ACTION_INVESTIGATE_IN_RESOLVER } from '../../../../timelines/components/timeline/body/translations'; import { Ecs } from '../../../../../common/ecs'; @@ -35,13 +39,23 @@ export const useInvestigateInResolverContextItem = ({ }: InvestigateInResolverProps) => { const dispatch = useDispatch(); const isDisabled = useMemo(() => !isInvestigateInResolverActionEnabled(ecsData), [ecsData]); + const { setGlobalFullScreen } = useGlobalFullScreen(); + const { setTimelineFullScreen } = useTimelineFullScreen(); const handleClick = useCallback(() => { + const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen'); dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: ecsData._id })); if (timelineId === TimelineId.active) { + if (dataGridIsFullScreen) { + setTimelineFullScreen(true); + } dispatch(setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.graph })); + } else { + if (dataGridIsFullScreen) { + setGlobalFullScreen(true); + } } onClose(); - }, [dispatch, ecsData._id, onClose, timelineId]); + }, [dispatch, ecsData._id, onClose, timelineId, setGlobalFullScreen, setTimelineFullScreen]); return isDisabled ? [] : [ diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 4a951dfff45d7a..0f544cf6301aaa 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -147,6 +147,9 @@ export class Plugin implements IPlugin { test('it has 100% width when isEventViewer is true and NOT in full screen mode', async () => { const wrapper = mount( - + ); @@ -71,7 +75,11 @@ describe('GraphOverlay', () => { const wrapper = mount( - + ); @@ -89,7 +97,11 @@ describe('GraphOverlay', () => { test('it has 100% width when isEventViewer is false and NOT in full screen mode', async () => { const wrapper = mount( - + ); @@ -111,7 +123,11 @@ describe('GraphOverlay', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx index a8cfea1de8e74c..ee7b820eb9179a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx @@ -24,6 +24,7 @@ import { FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../common/constants'; import { useGlobalFullScreen, useTimelineFullScreen, + closeDataGrid, } from '../../../common/containers/use_full_screen'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { TimelineId } from '../../../../common/types/timeline'; @@ -50,6 +51,15 @@ const OverlayContainer = styled.div` `} `; +const FullScreenOverlayContainer = styled.div` + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 10; +`; + const StyledResolver = styled(Resolver)` height: 100%; `; @@ -61,6 +71,7 @@ const FullScreenButtonIcon = styled(EuiButtonIcon)` interface OwnProps { isEventViewer: boolean; timelineId: TimelineId; + isDataGridExpanded: boolean; } interface NavigationProps { @@ -111,18 +122,19 @@ NavigationComponent.displayName = 'NavigationComponent'; const Navigation = React.memo(NavigationComponent); -const GraphOverlayComponent: React.FC = ({ isEventViewer, timelineId }) => { +const GraphOverlayComponent: React.FC = ({ + isEventViewer, + timelineId, + isDataGridExpanded, +}) => { const dispatch = useDispatch(); - const onCloseOverlay = useCallback(() => { - dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: '' })); - }, [dispatch, timelineId]); + const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); + const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const graphEventId = useDeepEqualSelector( (state) => (getTimeline(state, timelineId) ?? timelineDefaults).graphEventId ); - - const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); - const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); const getStartSelector = useMemo(() => startSelector(), []); const getEndSelector = useMemo(() => endSelector(), []); const getIsLoadingSelector = useMemo(() => isLoadingSelector(), []); @@ -149,11 +161,27 @@ const GraphOverlayComponent: React.FC = ({ isEventViewer, timelineId } } }); - const fullScreen = useMemo( + const isResolverExpanded = useMemo( () => isFullScreen({ globalFullScreen, timelineId, timelineFullScreen }), [globalFullScreen, timelineId, timelineFullScreen] ); + const fullScreen = useMemo(() => { + return isResolverExpanded && isDataGridExpanded; + }, [isResolverExpanded, isDataGridExpanded]); + + const onCloseOverlay = useCallback(() => { + if (isResolverExpanded === false) { + closeDataGrid(); + } + if (timelineId === TimelineId.active) { + setTimelineFullScreen(false); + } else { + setGlobalFullScreen(false); + } + dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: '' })); + }, [dispatch, timelineId, setTimelineFullScreen, setGlobalFullScreen, isResolverExpanded]); + const toggleFullScreen = useCallback(() => { if (timelineId === TimelineId.active) { setTimelineFullScreen(!timelineFullScreen); @@ -173,41 +201,74 @@ const GraphOverlayComponent: React.FC = ({ isEventViewer, timelineId } [] ); const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); - - return ( - - - - - + + + + + + + + {graphEventId !== undefined ? ( + - - - - {graphEventId !== undefined ? ( - - ) : ( - - + ) : ( + + + + )} + + ); + } else { + return ( + + + + + + - )} - - ); + + {graphEventId !== undefined ? ( + + ) : ( + + + + )} + + ); + } }; export const GraphOverlay = React.memo(GraphOverlayComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/graph_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/graph_tab_content/index.tsx index 1678a92c4cdaa8..f1d0db0d3ef553 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/graph_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/graph_tab_content/index.tsx @@ -26,7 +26,7 @@ const GraphTabContentComponent: React.FC = ({ timelineId } return null; } - return ; + return ; }; GraphTabContentComponent.displayName = 'GraphTabContentComponent'; diff --git a/x-pack/plugins/timelines/public/plugin.ts b/x-pack/plugins/timelines/public/plugin.ts index 74e1f2b32844a8..4b383ce3921477 100644 --- a/x-pack/plugins/timelines/public/plugin.ts +++ b/x-pack/plugins/timelines/public/plugin.ts @@ -48,6 +48,13 @@ export class TimelinesPlugin implements Plugin { return getHoverActions(this._store!); }, getTGrid: (props: TGridProps) => { + if (props.type === 'standalone' && this._store) { + const { getState } = this._store; + const state = getState(); + if (state && state.app) { + this._store = undefined; + } + } return getTGridLazy(props, { store: this._store, storage: this._storage, From fc0a93259bbe100f6683f0275a0c2617de670e6f Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 31 Aug 2021 15:50:46 -0400 Subject: [PATCH 2/6] Set EuiDataGrid height via mutation observer instead of 50ms setTimeout --- .../common/components/events_viewer/index.tsx | 113 +++++++---- .../containers/use_full_screen/index.tsx | 39 +++- .../components/t_grid/body/height_hack.ts | 49 +++-- .../public/components/t_grid/body/index.tsx | 58 +++--- .../components/t_grid/integrated/index.tsx | 181 ++++++++++-------- 5 files changed, 266 insertions(+), 174 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 4cb5794f91d001..6f579a39c094e2 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -25,6 +25,7 @@ import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_fe import { SourcererScopeName } from '../../store/sourcerer/model'; import { useSourcererScope } from '../../containers/sourcerer'; import type { EntityType } from '../../../../../timelines/common'; +import type { TGridType } from '../../../../../timelines/public'; import { TGridCellAction } from '../../../../../timelines/common/types'; import { DetailsPanel } from '../../../timelines/components/side_panel'; import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering'; @@ -160,9 +161,82 @@ const StatefulEventsViewerComponent: React.FC = ({ [graphEventId, id, isDataGridExpanded] ); + const tGridProps = useMemo(() => { + const type: TGridType = 'embedded'; + return { + id, + type, + browserFields, + columns, + dataProviders: dataProviders!, + defaultCellActions, + deletedEventIds, + docValueFields, + end, + entityType, + filters: globalFilters, + globalFullScreen, + graphOverlay, + hasAlertsCrud, + indexNames: selectedPatterns, + indexPattern, + isLive, + isLoadingIndexPattern, + itemsPerPage, + itemsPerPageOptions: itemsPerPageOptions!, + kqlMode, + query, + onRuleChange, + renderCellValue, + rowRenderers, + start, + sort, + additionalFilters, + graphEventId, + filterStatus: currentFilter, + leadingControlColumns, + trailingControlColumns, + tGridEventRenderedViewEnabled, + unit, + }; + }, [ + additionalFilters, + browserFields, + columns, + currentFilter, + dataProviders, + defaultCellActions, + deletedEventIds, + docValueFields, + end, + entityType, + globalFilters, + globalFullScreen, + graphEventId, + graphOverlay, + hasAlertsCrud, + id, + indexPattern, + isLive, + isLoadingIndexPattern, + itemsPerPage, + itemsPerPageOptions, + kqlMode, + onRuleChange, + query, + renderCellValue, + rowRenderers, + selectedPatterns, + sort, + start, + tGridEventRenderedViewEnabled, + trailingControlColumns, + unit, + ]); + return ( {(mutationRef) => ( @@ -170,42 +244,7 @@ const StatefulEventsViewerComponent: React.FC = ({ {tGridEnabled ? ( - timelinesUi.getTGrid<'embedded'>({ - id, - type: 'embedded', - browserFields, - columns, - dataProviders: dataProviders!, - defaultCellActions, - deletedEventIds, - docValueFields, - end, - entityType, - filters: globalFilters, - globalFullScreen, - graphOverlay, - hasAlertsCrud, - indexNames: selectedPatterns, - indexPattern, - isLive, - isLoadingIndexPattern, - itemsPerPage, - itemsPerPageOptions: itemsPerPageOptions!, - kqlMode, - query, - onRuleChange, - renderCellValue, - rowRenderers, - start, - sort, - additionalFilters, - graphEventId, - filterStatus: currentFilter, - leadingControlColumns, - trailingControlColumns, - tGridEventRenderedViewEnabled, - unit, - }) + timelinesUi.getTGrid<'embedded'>(tGridProps) ) : ( { }, 0); }; -export const closeDataGrid = () => { - const escapePress = new KeyboardEvent('click', { +const setDataGridFullScreen = (isExpanded: boolean) => { + const clickEvent = new KeyboardEvent('click', { view: window, bubbles: true, cancelable: true, }); - const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen'); + const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen') !== null; const dataGridFullScreenButtonNew = document.querySelector( '[data-test-subj="dataGridFullScrenButton"]' ); @@ -43,8 +43,27 @@ export const closeDataGrid = () => { '[data-test-subj="dataGridFullScreenButton"]' ); const dataGridFullScreenButton = dataGridFullScreenButtonNew || dataGridFullScreenButtonOld; + if (dataGridIsFullScreen !== isExpanded && dataGridFullScreenButton) { + dataGridFullScreenButton.dispatchEvent(clickEvent); + } +}; + +export const closeDataGrid = () => { + const clickEvent = new KeyboardEvent('click', { + view: window, + bubbles: true, + cancelable: true, + }); + const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen'); + const dataGridFullScreenButtonOld = document.querySelector( + '[data-test-subj="dataGridFullScrenButton"]' + ); + const dataGridFullScreenButtonNew = document.querySelector( + '[data-test-subj="dataGridFullScreenButton"]' + ); + const dataGridFullScreenButton = dataGridFullScreenButtonNew || dataGridFullScreenButtonOld; if (dataGridIsFullScreen && dataGridFullScreenButton) { - dataGridFullScreenButton.dispatchEvent(escapePress); + dataGridFullScreenButton.dispatchEvent(clickEvent); } }; @@ -55,10 +74,10 @@ export const expandDataGrid = () => { cancelable: true, }); const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen'); - const dataGridFullScreenButtonNew = document.querySelector( + const dataGridFullScreenButtonOld = document.querySelector( '[data-test-subj="dataGridFullScrenButton"]' ); - const dataGridFullScreenButtonOld = document.querySelector( + const dataGridFullScreenButtonNew = document.querySelector( '[data-test-subj="dataGridFullScreenButton"]' ); const dataGridFullScreenButton = dataGridFullScreenButtonNew || dataGridFullScreenButtonOld; @@ -84,11 +103,11 @@ export const useGlobalFullScreen = (): GlobalFullScreen => { const setGlobalFullScreen = useCallback( (fullScreen: boolean) => { if (fullScreen) { - expandDataGrid(); + setDataGridFullScreen(true); document.body.classList.add(SCROLLING_DISABLED_CLASS_NAME); resetScroll(); } else { - closeDataGrid(); + setDataGridFullScreen(false); document.body.classList.remove(SCROLLING_DISABLED_CLASS_NAME); resetScroll(); } @@ -114,9 +133,9 @@ export const useTimelineFullScreen = (): TimelineFullScreen => { const setTimelineFullScreen = useCallback( (fullScreen: boolean) => { if (fullScreen) { - expandDataGrid(); + setDataGridFullScreen(true); } else { - closeDataGrid(); + setDataGridFullScreen(false); } dispatch(inputsActions.setFullScreen({ id: 'timeline', fullScreen })); }, diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts index 542be06578d6b0..29852b41e91fb0 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts +++ b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts @@ -5,14 +5,11 @@ * 2.0. */ -import { useState, useLayoutEffect } from 'react'; +import { useState, useCallback, useEffect } from 'react'; // That could be different from security and observability. Get it as parameter? const INITIAL_DATA_GRID_HEIGHT = 967; -// It will recalculate DataGrid height after this time interval. -const TIME_INTERVAL = 50; - /** * You are probably asking yourself "Why 3?". But that is the wrong mindset. You should be asking yourself "why not 3?". * 3 (three) is a number, numeral and digit. It is the natural number following 2 and preceding 4, and is the smallest @@ -29,26 +26,26 @@ const MAGIC_GAP = 3; * * Please delete me and allow DataGrid to calculate its height when the bug is fixed. */ -export const useDataGridHeightHack = (pageSize: number, rowCount: number) => { - const [height, setHeight] = useState(INITIAL_DATA_GRID_HEIGHT); - - useLayoutEffect(() => { - setTimeout(() => { - const gridVirtualized = document.querySelector('#body-data-grid .euiDataGrid__virtualized'); - - if ( - gridVirtualized && - gridVirtualized.children[0].clientHeight !== gridVirtualized.clientHeight // check if it has vertical scroll - ) { - setHeight( - height + - gridVirtualized.children[0].clientHeight - - gridVirtualized.clientHeight + - MAGIC_GAP - ); - } - }, TIME_INTERVAL); - }, [pageSize, rowCount, height]); - - return height; +export const useDataGridHeightHack = () => { + const [dataGridHeight, setDataGridHeight] = useState(INITIAL_DATA_GRID_HEIGHT); + + const onMutation = useCallback(() => { + const gridVirtualized = document.querySelector('#body-data-grid .euiDataGrid__virtualized'); + + if ( + gridVirtualized && + gridVirtualized.children[0].clientHeight !== gridVirtualized.clientHeight // check if it has vertical scroll + ) { + setDataGridHeight( + dataGridHeight + + gridVirtualized.children[0].clientHeight - + gridVirtualized.clientHeight + + MAGIC_GAP + ); + } + }, [dataGridHeight]); + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(() => onMutation, []); + + return { height: dataGridHeight, onMutation }; }; diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 24f2718fbc1cfe..112bba62c35860 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -15,6 +15,7 @@ import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, + EuiMutationObserver, EuiProgress, } from '@elastic/eui'; import { getOr } from 'lodash/fp'; @@ -733,7 +734,7 @@ export const BodyComponent = React.memo( [id, loadPage, dispatch, clearSelected] ); - const height = useDataGridHeightHack(pageSize, data.length); + const { height, onMutation } = useDataGridHeightHack(); // Store context in state rather than creating object in provider value={} to prevent re-renders caused by a new object being created const [activeStatefulEventContext] = useState({ @@ -746,30 +747,37 @@ export const BodyComponent = React.memo( <> {tableView === 'gridView' && ( - ES_LIMIT_COUNT}> - - + + {(mutationRef) => ( + ES_LIMIT_COUNT} ref={mutationRef}> + + + )} + )} {tableView === 'eventRenderedView' && ( ({ flex-direction: column; `; +const FullWidthFlexGroup = styled(EuiFlexGroup)<{ $visible: boolean }>` + overflow: hidden; + margin: 0; + display: ${({ $visible }) => ($visible ? 'flex' : 'none')}; +`; + +const ScrollableFlexItem = styled(EuiFlexItem)` + overflow: auto; +`; + const SECURITY_ALERTS_CONSUMERS = [AlertConsumers.SIEM]; export interface TGridIntegratedProps { @@ -141,6 +164,7 @@ const TGridIntegratedComponent: React.FC = ({ id, indexNames, indexPattern, + isLive, isLoadingIndexPattern, itemsPerPage, itemsPerPageOptions, @@ -260,8 +284,6 @@ const TGridIntegratedComponent: React.FC = ({ setIsQueryLoading(loading); }, [loading]); - const alignItems = tableView === 'gridView' ? 'baseline' : 'center'; - const isFirstUpdate = useRef(true); useEffect(() => { if (isFirstUpdate.current && !loading) { @@ -269,6 +291,8 @@ const TGridIntegratedComponent: React.FC = ({ } }, [loading]); + const alignItems = tableView === 'gridView' ? 'baseline' : 'center'; + return ( = ({ {isFirstUpdate.current && } {graphOverlay} - - {canQueryTimeline && ( - - - - - - - {!resolverIsShowing(graphEventId) && additionalFilters} - - {tGridEventRenderedViewEnabled && entityType === 'alerts' && ( + {canQueryTimeline ? ( + <> + + - + - )} - - - {!graphEventId && graphOverlay == null && ( - <> - {totalCountMinusDeleted === 0 && loading === false && ( - - - - } - titleSize="s" - body={ -

- -

- } - /> - )} - {totalCountMinusDeleted > 0 && ( - + + {!resolverIsShowing(graphEventId) && additionalFilters} + + {tGridEventRenderedViewEnabled && entityType === 'alerts' && ( + + + )} - - )} -
- )} +
+ + + + {nonDeletedEvents.length === 0 && loading === false ? ( + + + + } + titleSize="s" + body={ +

+ +

+ } + /> + ) : ( + <> + + + )} +
+
+
+ + ) : null}
); From fc19c16bc4865886337240b2e8fec105153d7551 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 31 Aug 2021 17:00:32 -0400 Subject: [PATCH 3/6] Use previous hack over MutationObserver --- .../global_kql_header/index.tsx | 2 +- .../common/components/events_viewer/index.tsx | 8 +-- .../containers/use_full_screen/index.tsx | 53 ++++------------- .../components/graph_overlay/index.tsx | 6 +- .../components/t_grid/body/height_hack.ts | 51 ++++++++-------- .../public/components/t_grid/body/index.tsx | 59 ++++++++----------- 6 files changed, 72 insertions(+), 107 deletions(-) diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx index 04df8d17e2d389..3e3c91133eab61 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx @@ -11,7 +11,7 @@ import { useGlobalHeaderPortal } from '../../../../common/hooks/use_global_heade const StyledStickyWrapper = styled.div` position: sticky; - z-index: 5; + z-index: ${(props) => props.theme.eui.euiZLevel2}; // TOP location is declared in src/public/rendering/_base.scss to keep in line with Kibana Chrome `; diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 6f579a39c094e2..02ba3348c4b750 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -20,7 +20,7 @@ import type { SubsetTimelineModel, TimelineModel } from '../../../timelines/stor import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { Filter } from '../../../../../../../src/plugins/data/public'; import { InspectButtonContainer } from '../inspect'; -import { useGlobalFullScreen } from '../../containers/use_full_screen'; +import { useGlobalFullScreen, useIsDataGridExpanded } from '../../containers/use_full_screen'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useSourcererScope } from '../../containers/sourcerer'; @@ -144,11 +144,7 @@ const StatefulEventsViewerComponent: React.FC = ({ const globalFilters = useMemo(() => [...filters, ...(pageFilters ?? [])], [filters, pageFilters]); const trailingControlColumns: ControlColumnProps[] = EMPTY_CONTROL_COLUMNS; - const [isDataGridExpanded, setDataGridExpanded] = useState(false); - const onMutation = useCallback(() => { - const isExpanded = document.querySelector('.euiDataGrid--fullScreen') !== null; - setDataGridExpanded(isExpanded); - }, [setDataGridExpanded]); + const { isDataGridExpanded, onMutation } = useIsDataGridExpanded(); const graphOverlay = useMemo( () => graphEventId != null && graphEventId.length > 0 ? ( diff --git a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx index 8e9a49810af354..2afdfa4f3ea6de 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { useCallback, useMemo } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { SCROLLING_DISABLED_CLASS_NAME } from '../../../../common/constants'; @@ -29,7 +29,7 @@ export const resetScroll = () => { }, 0); }; -const setDataGridFullScreen = (isExpanded: boolean) => { +export const setDataGridFullScreen = (isExpanded: boolean) => { const clickEvent = new KeyboardEvent('click', { view: window, bubbles: true, @@ -48,44 +48,6 @@ const setDataGridFullScreen = (isExpanded: boolean) => { } }; -export const closeDataGrid = () => { - const clickEvent = new KeyboardEvent('click', { - view: window, - bubbles: true, - cancelable: true, - }); - const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen'); - const dataGridFullScreenButtonOld = document.querySelector( - '[data-test-subj="dataGridFullScrenButton"]' - ); - const dataGridFullScreenButtonNew = document.querySelector( - '[data-test-subj="dataGridFullScreenButton"]' - ); - const dataGridFullScreenButton = dataGridFullScreenButtonNew || dataGridFullScreenButtonOld; - if (dataGridIsFullScreen && dataGridFullScreenButton) { - dataGridFullScreenButton.dispatchEvent(clickEvent); - } -}; - -export const expandDataGrid = () => { - const clickEvent = new MouseEvent('click', { - view: window, - bubbles: true, - cancelable: true, - }); - const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen'); - const dataGridFullScreenButtonOld = document.querySelector( - '[data-test-subj="dataGridFullScrenButton"]' - ); - const dataGridFullScreenButtonNew = document.querySelector( - '[data-test-subj="dataGridFullScreenButton"]' - ); - const dataGridFullScreenButton = dataGridFullScreenButtonNew || dataGridFullScreenButtonOld; - if (dataGridFullScreenButton && !dataGridIsFullScreen) { - dataGridFullScreenButton.dispatchEvent(clickEvent); - } -}; - interface GlobalFullScreen { globalFullScreen: boolean; setGlobalFullScreen: (fullScreen: boolean) => void; @@ -150,3 +112,14 @@ export const useTimelineFullScreen = (): TimelineFullScreen => { ); return memoizedReturn; }; + +export const useIsDataGridExpanded = () => { + const [isDataGridExpanded, setDataGridExpanded] = useState(false); + const onMutation = useCallback(() => { + const isExpanded = document.querySelector('.euiDataGrid--fullScreen') !== null; + setDataGridExpanded(isExpanded); + }, [setDataGridExpanded]); + return useMemo(() => { + return { onMutation, isDataGridExpanded }; + }, [onMutation, isDataGridExpanded]); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx index ee7b820eb9179a..eb8a1acea6b25c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx @@ -24,7 +24,7 @@ import { FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../common/constants'; import { useGlobalFullScreen, useTimelineFullScreen, - closeDataGrid, + setDataGridFullScreen, } from '../../../common/containers/use_full_screen'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { TimelineId } from '../../../../common/types/timeline'; @@ -57,7 +57,7 @@ const FullScreenOverlayContainer = styled.div` bottom: 0; left: 0; right: 0; - z-index: 10; + z-index: ${(props) => props.theme.eui.euiZLevel3}; `; const StyledResolver = styled(Resolver)` @@ -172,7 +172,7 @@ const GraphOverlayComponent: React.FC = ({ const onCloseOverlay = useCallback(() => { if (isResolverExpanded === false) { - closeDataGrid(); + setDataGridFullScreen(false); } if (timelineId === TimelineId.active) { setTimelineFullScreen(false); diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts index 29852b41e91fb0..eb1cb9d006101c 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts +++ b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts @@ -5,11 +5,14 @@ * 2.0. */ -import { useState, useCallback, useEffect } from 'react'; +import { useState, useLayoutEffect } from 'react'; // That could be different from security and observability. Get it as parameter? const INITIAL_DATA_GRID_HEIGHT = 967; +// It will recalculate DataGrid height after this time interval. +const TIME_INTERVAL = 50; + /** * You are probably asking yourself "Why 3?". But that is the wrong mindset. You should be asking yourself "why not 3?". * 3 (three) is a number, numeral and digit. It is the natural number following 2 and preceding 4, and is the smallest @@ -26,26 +29,28 @@ const MAGIC_GAP = 3; * * Please delete me and allow DataGrid to calculate its height when the bug is fixed. */ -export const useDataGridHeightHack = () => { - const [dataGridHeight, setDataGridHeight] = useState(INITIAL_DATA_GRID_HEIGHT); - - const onMutation = useCallback(() => { - const gridVirtualized = document.querySelector('#body-data-grid .euiDataGrid__virtualized'); - - if ( - gridVirtualized && - gridVirtualized.children[0].clientHeight !== gridVirtualized.clientHeight // check if it has vertical scroll - ) { - setDataGridHeight( - dataGridHeight + - gridVirtualized.children[0].clientHeight - - gridVirtualized.clientHeight + - MAGIC_GAP - ); - } - }, [dataGridHeight]); - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => onMutation, []); - - return { height: dataGridHeight, onMutation }; +export const useDataGridHeightHack = (pageSize: number, rowCount: number) => { + const [height, setHeight] = useState(INITIAL_DATA_GRID_HEIGHT); + + useLayoutEffect(() => { + setTimeout(() => { + const gridVirtualized = document.querySelector('#body-data-grid .euiDataGrid__virtualized'); + + if ( + gridVirtualized && + gridVirtualized.children[0].clientHeight !== gridVirtualized.clientHeight // check if it has vertical scroll + ) { + setHeight((currentHeight) => { + return ( + currentHeight + + gridVirtualized.children[0].clientHeight - + gridVirtualized.clientHeight + + MAGIC_GAP + ); + }); + } + }, TIME_INTERVAL); + }, [pageSize, rowCount]); + + return height; }; diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 112bba62c35860..dbef74b4380e0c 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -15,7 +15,6 @@ import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, - EuiMutationObserver, EuiProgress, } from '@elastic/eui'; import { getOr } from 'lodash/fp'; @@ -734,8 +733,7 @@ export const BodyComponent = React.memo( [id, loadPage, dispatch, clearSelected] ); - const { height, onMutation } = useDataGridHeightHack(); - + const height = useDataGridHeightHack(pageSize, data.length); // Store context in state rather than creating object in provider value={} to prevent re-renders caused by a new object being created const [activeStatefulEventContext] = useState({ timelineID: id, @@ -747,37 +745,30 @@ export const BodyComponent = React.memo( <> {tableView === 'gridView' && ( - - {(mutationRef) => ( - ES_LIMIT_COUNT} ref={mutationRef}> - - - )} - + ES_LIMIT_COUNT}> + + )} {tableView === 'eventRenderedView' && ( Date: Tue, 31 Aug 2021 19:06:47 -0400 Subject: [PATCH 4/6] Fix failing test --- .../timelines/components/graph_overlay/index.test.tsx | 4 ++-- .../public/components/t_grid/integrated/index.tsx | 9 +-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx index af00053c86548f..10fa7ef0195d32 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx @@ -63,7 +63,7 @@ describe('GraphOverlay', () => { }); }); - test('it has a calculated width that makes room for the Timeline flyout button when isEventViewer is true in full screen mode', async () => { + test('it has a fixed position when isEventViewer is true in full screen mode', async () => { (useGlobalFullScreen as jest.Mock).mockReturnValue({ globalFullScreen: true, // <-- true when an events viewer is in full screen mode setGlobalFullScreen: jest.fn(), @@ -85,7 +85,7 @@ describe('GraphOverlay', () => { await waitFor(() => { const overlayContainer = wrapper.find('[data-test-subj="overlayContainer"]').first(); - expect(overlayContainer).toHaveStyleRule('width', 'calc(100% - 36px)'); + expect(overlayContainer).toHaveStyleRule('position', 'fixed'); }); }); }); diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx index 3c2be90bf89a44..3540e70f63ffef 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx @@ -13,7 +13,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, - EuiProgress, EuiLoadingContent, } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; @@ -46,16 +45,10 @@ import { } from '../../../../../../../src/plugins/data/public'; import { useDeepEqualSelector } from '../../../hooks/use_selector'; import { defaultHeaders } from '../body/column_headers/default_headers'; -import { - calculateTotalPages, - buildCombinedQuery, - getCombinedFilterQuery, - resolverIsShowing, -} from '../helpers'; +import { buildCombinedQuery, getCombinedFilterQuery, resolverIsShowing } from '../helpers'; import { tGridActions, tGridSelectors } from '../../../store/t_grid'; import { useTimelineEvents } from '../../../container'; import { StatefulBody } from '../body'; -import { Footer, footerHeight } from '../footer'; import { SELECTOR_TIMELINE_GLOBAL_CONTAINER, UpdatedFlexGroup, UpdatedFlexItem } from '../styles'; import { Sort } from '../body/sort'; import { InspectButton, InspectButtonContainer } from '../../inspect'; From 278b4fe9951e5fb6c84fdd90422330f0ed221227 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 31 Aug 2021 21:55:56 -0400 Subject: [PATCH 5/6] Remove duplicated code w/Xavier, fix test --- .../public/common/components/events_viewer/index.tsx | 2 +- x-pack/plugins/security_solution/public/plugin.tsx | 3 --- .../timelines/components/graph_overlay/index.test.tsx | 2 +- x-pack/plugins/timelines/public/plugin.ts | 7 ------- 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 02ba3348c4b750..a063e06ded46ed 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo, useEffect, useCallback, useState } from 'react'; +import React, { useMemo, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { EuiMutationObserver } from '@elastic/eui'; import deepEqual from 'fast-deep-equal'; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 0f544cf6301aaa..4a951dfff45d7a 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -147,9 +147,6 @@ export class Plugin implements IPlugin { ); diff --git a/x-pack/plugins/timelines/public/plugin.ts b/x-pack/plugins/timelines/public/plugin.ts index 4b383ce3921477..74e1f2b32844a8 100644 --- a/x-pack/plugins/timelines/public/plugin.ts +++ b/x-pack/plugins/timelines/public/plugin.ts @@ -48,13 +48,6 @@ export class TimelinesPlugin implements Plugin { return getHoverActions(this._store!); }, getTGrid: (props: TGridProps) => { - if (props.type === 'standalone' && this._store) { - const { getState } = this._store; - const state = getState(); - if (state && state.app) { - this._store = undefined; - } - } return getTGridLazy(props, { store: this._store, storage: this._storage, From bf54b4f440abaf09fc9ba98a932d6e041d36f737 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 31 Aug 2021 22:01:05 -0400 Subject: [PATCH 6/6] Don't open EuiDataGrid when opening fullscreen popover timeline --- .../public/common/containers/use_full_screen/index.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx index 2afdfa4f3ea6de..5271bdfd47bb22 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx @@ -94,11 +94,6 @@ export const useTimelineFullScreen = (): TimelineFullScreen => { useShallowEqualSelector(inputsSelectors.timelineFullScreenSelector) ?? false; const setTimelineFullScreen = useCallback( (fullScreen: boolean) => { - if (fullScreen) { - setDataGridFullScreen(true); - } else { - setDataGridFullScreen(false); - } dispatch(inputsActions.setFullScreen({ id: 'timeline', fullScreen })); }, [dispatch]