From 887cc1218bdb7d61c86660e5e7af267ae277ac0f Mon Sep 17 00:00:00 2001 From: Andrew Goldstein Date: Wed, 15 Jul 2020 06:08:34 -0600 Subject: [PATCH] [Security Solution] Full screen timeline, Collapse event (#71786) (#71838) ## Full screen Timeline & Timeline-based views - Adds a _Full screen_ mode to Timeline, and all Timeline-based views, including: - Detections - Detections > Rule details - Hosts > Events - Hosts > External alerts - Network > External alerts - Timeline - Enter full screen from any Resolver - Adds a `Collapse event` action for quickly collapsing an expanded Timeline event - Hides the `Add to case action` in timeline-based Resolver views, so those actions are only enabled in Timeline (a `TODO` from https://github.com/elastic/kibana/pull/70111) ### Full screen detections ![full-screen-detections](https://user-images.githubusercontent.com/4459398/87493332-d348f280-c609-11ea-9399-126d2259daa2.gif) ### Enter full screen from any Resolver ![full-screen-resolver](https://user-images.githubusercontent.com/4459398/87493348-de038780-c609-11ea-86a3-52ab24055e38.gif) ### Full screen Timeline ![full-screen-timeline](https://user-images.githubusercontent.com/4459398/87493394-f4114800-c609-11ea-8d62-4add291d937a.gif) ### Collapse event ![collapse-event](https://user-images.githubusercontent.com/4459398/87493408-fa9fbf80-c609-11ea-88c8-fa87d82d1eb1.gif) ### Sort tooltip ![sort-tooltip](https://user-images.githubusercontent.com/4459398/87493417-012e3700-c60a-11ea-9905-44e3b7cfe60f.gif) --- .../security_solution/common/constants.ts | 2 + .../public/app/home/index.tsx | 2 +- .../components/all_cases/columns.test.tsx | 1 + .../cases/components/all_cases/index.test.tsx | 2 + .../components/all_cases_modal/index.test.tsx | 1 + .../cases/components/case_view/index.test.tsx | 1 + .../configure_cases/button.test.tsx | 1 + .../use_push_to_service/index.test.tsx | 2 + .../components/alerts_viewer/alerts_table.tsx | 3 + .../common/components/alerts_viewer/index.tsx | 47 +- .../components/autocomplete/helpers.test.ts | 1 + .../components/charts/barchart.test.tsx | 1 + .../charts/draggable_legend.test.tsx | 1 + .../charts/draggable_legend_item.test.tsx | 1 + .../drag_and_drop/draggable_wrapper.test.tsx | 1 + .../draggable_wrapper_hover_content.test.tsx | 1 + .../components/draggables/index.test.tsx | 1 + .../__snapshots__/event_details.test.tsx.snap | 27 + .../event_details/event_details.test.tsx | 30 + .../event_details/event_details.tsx | 54 +- .../event_fields_browser.test.tsx | 1 + .../event_details/stateful_event_details.tsx | 13 +- .../events_viewer/events_viewer.test.tsx | 1 + .../events_viewer/events_viewer.tsx | 64 +- .../components/events_viewer/index.test.tsx | 1 + .../common/components/events_viewer/index.tsx | 5 + .../components/exit_full_screen/index.tsx | 49 + .../exit_full_screen/translations.ts | 11 + .../filters_global/filters_global.tsx | 2 + .../common/components/header_global/index.tsx | 9 +- .../header_page/editable_title.test.tsx | 1 + .../components/header_page/index.test.tsx | 1 + .../components/header_page/title.test.tsx | 1 + .../__snapshots__/index.test.tsx.snap | 4 +- .../components/header_section/index.tsx | 12 +- .../components/ml/entity_draggable.test.tsx | 2 + .../ml/score/anomaly_score.test.tsx | 2 + .../ml/score/anomaly_scores.test.tsx | 2 + .../ml/score/draggable_score.test.tsx | 4 +- .../get_anomalies_host_table_columns.test.tsx | 4 +- ...t_anomalies_network_table_columns.test.tsx | 1 + .../public/common/components/page/index.tsx | 8 +- .../common/components/tables/helpers.test.tsx | 6 +- .../common/components/top_n/index.test.tsx | 1 + .../common/components/top_n/top_n.test.tsx | 1 + .../common/components/wrapper_page/index.tsx | 8 +- .../containers/use_full_screen/index.tsx | 39 + .../public/common/store/inputs/actions.ts | 5 + .../public/common/store/inputs/helpers.ts | 16 + .../public/common/store/inputs/model.ts | 1 + .../public/common/store/inputs/reducer.ts | 9 + .../public/common/store/inputs/selectors.ts | 7 + .../alerts_histogram.test.tsx | 1 + .../alerts_histogram_panel/index.test.tsx | 1 + .../components/alerts_table/index.test.tsx | 1 + .../components/alerts_table/index.tsx | 3 + .../index.test.tsx | 1 + .../rules/all_rules_tables/index.test.tsx | 1 + .../load_empty_prompt.test.tsx | 1 + .../detection_engine.test.tsx | 51 +- .../detection_engine/detection_engine.tsx | 103 +- .../rules/all/columns.test.tsx | 1 + .../detection_engine/rules/all/index.test.tsx | 1 + .../rules/create/index.test.tsx | 1 + .../rules/details/index.test.tsx | 51 +- .../detection_engine/rules/details/index.tsx | 261 ++-- .../rules/edit/index.test.tsx | 1 + .../detection_engine/rules/index.test.tsx | 1 + .../authentications_table/index.test.tsx | 1 + .../components/hosts_table/index.test.tsx | 1 + .../hosts/components/kpi_hosts/index.test.tsx | 1 + .../uncommon_process_table/index.test.tsx | 1 + .../hosts/pages/details/details_tabs.test.tsx | 1 + .../public/hosts/pages/display.tsx | 13 + .../public/hosts/pages/hosts.tsx | 77 +- .../navigation/events_query_tab_body.tsx | 49 +- .../components/direction/direction.test.tsx | 1 + .../embeddables/embedded_map.test.tsx | 1 + .../line_tool_tip_content.test.tsx | 2 + .../map_tool_tip/map_tool_tip.test.tsx | 1 + .../point_tool_tip_content.test.tsx | 2 + .../index.test.tsx | 1 + .../network/components/ip/index.test.tsx | 1 + .../components/ip_overview/index.test.tsx | 1 + .../components/kpi_network/index.test.tsx | 1 + .../network_dns_table/index.test.tsx | 1 + .../network_http_table/index.test.tsx | 1 + .../index.test.tsx | 1 + .../network_top_n_flow_table/index.test.tsx | 1 + .../network/components/port/index.test.tsx | 1 + .../source_destination/index.test.tsx | 1 + .../source_destination_ip.test.tsx | 1 + .../components/tls_table/index.test.tsx | 1 + .../components/users_table/index.test.tsx | 1 + .../public/network/pages/network.tsx | 95 +- .../alerts_by_category/index.test.tsx | 1 + .../components/event_counts/index.test.tsx | 1 + .../endpoint_overview/index.test.tsx | 2 + .../components/host_overview/index.test.tsx | 1 + .../components/overview_host/index.test.tsx | 1 + .../overview_network/index.test.tsx | 1 + .../certificate_fingerprint/index.test.tsx | 1 + .../components/duration/index.test.tsx | 1 + .../field_renderers/field_renderers.test.tsx | 1 + .../fields_browser/category.test.tsx | 1 + .../fields_browser/field_browser.test.tsx | 1 + .../fields_browser/field_items.test.tsx | 1 + .../fields_browser/field_name.test.tsx | 1 + .../fields_browser/fields_pane.test.tsx | 1 + .../components/fields_browser/index.test.tsx | 1 + .../header_with_close_button/index.test.tsx | 1 + .../components/flyout/pane/index.tsx | 16 +- .../flyout/pane/timeline_resize_handle.tsx | 14 +- .../components/graph_overlay/index.tsx | 99 +- .../components/ja3_fingerprint/index.test.tsx | 1 + .../components/netflow/index.test.tsx | 1 + .../components/open_timeline/index.test.tsx | 1 + .../open_timeline/open_timeline.test.tsx | 1 + .../open_timeline_modal_body.test.tsx | 1 + .../timelines_table/actions_columns.test.tsx | 1 + .../timelines_table/common_columns.test.tsx | 1 + .../timelines_table/extended_columns.test.tsx | 1 + .../icon_header_columns.test.tsx | 1 + .../timelines_table/index.test.tsx | 1 + .../__snapshots__/index.test.tsx.snap | 1046 +++++++++-------- .../body/column_headers/helpers.test.ts | 7 +- .../body/column_headers/index.test.tsx | 35 +- .../timeline/body/column_headers/index.tsx | 64 +- .../body/column_headers/translations.ts | 4 + .../components/timeline/body/constants.ts | 4 +- .../body/data_driven_columns/index.test.tsx | 1 + .../timeline/body/events/stateful_event.tsx | 1 + .../components/timeline/body/helpers.ts | 35 + .../components/timeline/body/index.test.tsx | 1 + .../components/timeline/body/index.tsx | 48 +- .../timeline/body/renderers/args.test.tsx | 1 + .../renderers/auditd/generic_details.test.tsx | 1 + .../auditd/generic_file_details.test.tsx | 1 + .../primary_secondary_user_info.test.tsx | 1 + .../session_user_host_working_dir.test.tsx | 1 + .../body/renderers/bytes/index.test.tsx | 1 + .../dns/dns_request_event_details.test.tsx | 1 + .../dns_request_event_details_line.test.tsx | 2 +- .../renderers/empty_column_renderer.test.tsx | 1 + .../endgame_security_event_details.test.tsx | 1 + ...dgame_security_event_details_line.test.tsx | 1 + .../renderers/exit_code_draggable.test.tsx | 1 + .../body/renderers/file_draggable.test.tsx | 1 + .../body/renderers/formatted_field.test.tsx | 1 + .../renderers/get_column_renderer.test.tsx | 1 + .../body/renderers/get_row_renderer.test.tsx | 1 + .../body/renderers/host_working_dir.test.tsx | 1 + .../netflow/netflow_row_renderer.test.tsx | 1 + .../parent_process_draggable.test.tsx | 1 + .../renderers/plain_column_renderer.test.tsx | 1 + .../body/renderers/process_draggable.test.tsx | 1 + .../body/renderers/process_hash.test.tsx | 1 + .../suricata/suricata_details.test.tsx | 1 + .../suricata/suricata_row_renderer.test.tsx | 1 + .../suricata/suricata_signature.test.tsx | 1 + .../body/renderers/system/auth_ssh.test.tsx | 1 + .../renderers/system/generic_details.test.tsx | 1 + .../system/generic_file_details.test.tsx | 1 + .../body/renderers/system/package.test.tsx | 1 + .../renderers/user_host_working_dir.test.tsx | 1 + .../body/renderers/zeek/zeek_details.test.tsx | 1 + .../renderers/zeek/zeek_row_renderer.test.tsx | 1 + .../renderers/zeek/zeek_signature.test.tsx | 1 + .../sort_indicator.test.tsx.snap | 15 +- .../body/sort/sort_indicator.test.tsx | 43 +- .../timeline/body/sort/sort_indicator.tsx | 26 +- .../components/timeline/body/translations.ts | 21 + .../timeline/expandable_event/index.tsx | 3 + .../components/timeline/index.test.tsx | 1 + .../timeline/properties/index.test.tsx | 1 + .../properties/use_create_timeline.test.tsx | 20 +- .../properties/use_create_timeline.tsx | 19 +- .../components/timeline/timeline.test.tsx | 1 + .../timeline/epic_local_storage.test.tsx | 1 + 179 files changed, 1927 insertions(+), 870 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/exit_full_screen/index.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/exit_full_screen/translations.ts create mode 100644 x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx create mode 100644 x-pack/plugins/security_solution/public/hosts/pages/display.tsx diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index e5dd109007eab..b39a038c4cc3c 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -32,6 +32,8 @@ export const DEFAULT_INTERVAL_PAUSE = true; export const DEFAULT_INTERVAL_TYPE = 'manual'; export const DEFAULT_INTERVAL_VALUE = 300000; // ms export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges'; +export const FILTERS_GLOBAL_HEIGHT = 109; // px +export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled'; export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51'; export const ENDPOINT_METADATA_INDEX = 'metrics-endpoint.metadata-*'; diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index 8f03945df437c..41b9252c67b8a 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -32,7 +32,7 @@ Main.displayName = 'Main'; const usersViewing = ['elastic']; // TODO: get the users viewing this timeline from Elasticsearch (persistance) /** the global Kibana navigation at the top of every page */ -const globalHeaderHeightPx = 48; +export const globalHeaderHeightPx = 48; const calculateFlyoutHeight = ({ globalHeaderSize, diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx index 9db8adbf9346f..654a5f5c4a599 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { mount } from 'enzyme'; +import '../../../common/mock/match_media'; import { ExternalServiceColumn } from './columns'; import { useGetCasesMockState } from '../../containers/mock'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx index d8acda8ec4f33..23cabd6778cc0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx @@ -7,6 +7,8 @@ import React from 'react'; import { mount } from 'enzyme'; import moment from 'moment-timezone'; + +import '../../../common/mock/match_media'; import { AllCases } from '.'; import { TestProviders } from '../../../common/mock'; import { useGetCasesMockState } from '../../containers/mock'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx index f4fd7cc67224f..b93de014f5c18 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases_modal/index.test.tsx @@ -5,6 +5,7 @@ */ import { mount } from 'enzyme'; import React from 'react'; +import '../../../common/mock/match_media'; import { AllCasesModal } from '.'; import { TestProviders } from '../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx index 2832a28fbb7cd..b93df325b5a8b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { mount } from 'enzyme'; +import '../../../common/mock/match_media'; import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; import { CaseComponent, CaseProps, CaseView } from '.'; import { basicCase, basicCaseClosed, caseUserActions } from '../../containers/mock'; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx index 8d14b2357f450..6fb693e47560d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { ReactWrapper, mount } from 'enzyme'; import { EuiText } from '@elastic/eui'; +import '../../../common/mock/match_media'; import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button'; import { TestProviders } from '../../../common/mock'; import { searchURL } from './__mock__'; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx index d17a2bd215910..eb80eaff578f5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx @@ -6,6 +6,8 @@ /* eslint-disable react/display-name */ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; + +import '../../../common/mock/match_media'; import { usePushToService, ReturnUsePushToService, UsePushToService } from '.'; import { TestProviders } from '../../../common/mock'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx index 841a1ef09ede6..e30560f6c8147 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx @@ -58,6 +58,7 @@ const defaultAlertsFilters: Filter[] = [ interface Props { timelineId: TimelineIdLiteral; endDate: string; + eventsViewerBodyHeight?: number; startDate: string; pageFilters?: Filter[]; } @@ -65,6 +66,7 @@ interface Props { const AlertsTableComponent: React.FC = ({ timelineId, endDate, + eventsViewerBodyHeight, startDate, pageFilters = [], }) => { @@ -91,6 +93,7 @@ const AlertsTableComponent: React.FC = ({ pageFilters={alertsFilter} defaultModel={alertsDefaultModel} end={endDate} + height={eventsViewerBodyHeight} id={timelineId} start={startDate} /> diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx index a31cb4f2a8bfd..832b14f00159a 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx @@ -5,8 +5,18 @@ */ import React, { useEffect, useCallback, useMemo } from 'react'; import numeral from '@elastic/numeral'; +import { useWindowSize } from 'react-use'; + +import { globalHeaderHeightPx } from '../../../app/home'; +import { DEFAULT_NUMBER_FORMAT, FILTERS_GLOBAL_HEIGHT } from '../../../../common/constants'; +import { useFullScreen } from '../../containers/use_full_screen'; +import { EVENTS_VIEWER_HEADER_HEIGHT } from '../events_viewer/events_viewer'; +import { + getEventsViewerBodyHeight, + MIN_EVENTS_VIEWER_BODY_HEIGHT, +} from '../../../timelines/components/timeline/body/helpers'; +import { footerHeight } from '../../../timelines/components/timeline/footer'; -import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; import { AlertsComponentsProps } from './types'; import { AlertsTable } from './alerts_table'; import * as i18n from './translations'; @@ -35,6 +45,8 @@ export const AlertsView = ({ // eslint-disable-next-line react-hooks/exhaustive-deps [] ); + const { height: windowHeight } = useWindowSize(); + const { globalFullScreen } = useFullScreen(); const alertsHistogramConfigs: MatrixHisrogramConfigs = useMemo( () => ({ ...histogramConfigs, @@ -52,19 +64,32 @@ export const AlertsView = ({ return ( <> - + {!globalFullScreen && ( + + )} diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts index c2e8e56084452..cfe23b9391ec0 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../common/mock/match_media'; import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts'; import { diff --git a/x-pack/plugins/security_solution/public/common/components/charts/barchart.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/barchart.test.tsx index 49c421c5680ba..8617388f4ffb5 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/barchart.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/barchart.test.tsx @@ -12,6 +12,7 @@ import { ThemeProvider } from 'styled-components'; import { escapeDataProviderId } from '../drag_and_drop/helpers'; import { TestProviders } from '../../mock'; +import '../../mock/match_media'; import { BarChartBaseComponent, BarChartComponent } from './barchart'; import { ChartSeriesData } from './common'; diff --git a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.test.tsx index a11fdda3d1b3a..8fd2fa1fdef12 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend.test.tsx @@ -9,6 +9,7 @@ import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; import { ThemeProvider } from 'styled-components'; +import '../../mock/match_media'; import { TestProviders } from '../../mock'; import { MIN_LEGEND_HEIGHT, DraggableLegend } from './draggable_legend'; diff --git a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx index 8ff75c8ca0780..9f6e614c3c285 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/draggable_legend_item.test.tsx @@ -9,6 +9,7 @@ import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; import { ThemeProvider } from 'styled-components'; +import '../../mock/match_media'; import { TestProviders } from '../../mock'; import { DraggableLegendItem, LegendItem } from './draggable_legend_item'; diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx index d1b3b671307d1..da68280ed760c 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { DraggableStateSnapshot, DraggingStyle } from 'react-beautiful-dnd'; +import '../../mock/match_media'; import { mockBrowserFields, mocksSource } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; import { mockDataProviders } from '../../../timelines/components/timeline/data_providers/mock/mock_data_providers'; diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx index 432e369cdd0f6..3f06a8168b5ce 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { useWithSource } from '../../containers/source'; import { mockBrowserFields } from '../../containers/source/mock'; +import '../../mock/match_media'; import { useKibana } from '../../lib/kibana'; import { TestProviders } from '../../mock'; import { createKibanaCoreStartMock } from '../../mock/kibana_core'; diff --git a/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx index 3d80a2605418e..ff1679875865c 100644 --- a/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/draggables/index.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import { TestProviders } from '../../mock'; +import '../../mock/match_media'; import { getEmptyString } from '../empty_value'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap index 9ca9cd6cce389..ebaf60e7078f0 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap @@ -4,6 +4,33 @@ exports[`EventDetails rendering should match snapshot 1`] = `
+ + + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={false} + panelPaddingSize="m" + repositionOnScroll={true} + /> + { data={mockDetailItemData} id={mockDetailItemDataId} view="table-view" + onEventToggled={jest.fn()} onUpdateColumns={jest.fn()} onViewSelected={jest.fn()} timelineId="test" @@ -50,6 +52,7 @@ describe('EventDetails', () => { data={mockDetailItemData} id={mockDetailItemDataId} view="table-view" + onEventToggled={jest.fn()} onUpdateColumns={jest.fn()} onViewSelected={jest.fn()} timelineId="test" @@ -76,6 +79,7 @@ describe('EventDetails', () => { data={mockDetailItemData} id={mockDetailItemDataId} view="table-view" + onEventToggled={jest.fn()} onUpdateColumns={jest.fn()} onViewSelected={jest.fn()} timelineId="test" @@ -88,5 +92,31 @@ describe('EventDetails', () => { wrapper.find('[data-test-subj="eventDetails"]').find('.euiTab-isSelected').first().text() ).toEqual('Table'); }); + + test('it invokes `onEventToggled` when the collapse button is clicked', () => { + const onEventToggled = jest.fn(); + + const wrapper = mount( + + + + ); + + wrapper.find('[data-test-subj="collapse"]').first().simulate('click'); + wrapper.update(); + + expect(onEventToggled).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index c28757a90c702..53ec14380d5bc 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -4,8 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; -import React from 'react'; +import { noop } from 'lodash/fp'; +import { + EuiButtonIcon, + EuiPopover, + EuiTabbedContent, + EuiTabbedContentTab, + EuiToolTip, +} from '@elastic/eui'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; @@ -15,15 +22,34 @@ import { OnUpdateColumns } from '../../../timelines/components/timeline/events'; import { EventFieldsBrowser } from './event_fields_browser'; import { JsonView } from './json_view'; import * as i18n from './translations'; +import { COLLAPSE, COLLAPSE_EVENT } from '../../../timelines/components/timeline/body/translations'; export type View = 'table-view' | 'json-view'; +const PopoverContainer = styled.div` + left: -40px; + position: relative; + top: 10px; + + .euiPopover { + position: fixed; + z-index: 10; + } +`; + +const CollapseButton = styled(EuiButtonIcon)` + border: 1px solid; +`; + +CollapseButton.displayName = 'CollapseButton'; + interface Props { browserFields: BrowserFields; columnHeaders: ColumnHeaderOptions[]; data: DetailItem[]; id: string; view: View; + onEventToggled: () => void; onUpdateColumns: OnUpdateColumns; onViewSelected: (selected: View) => void; timelineId: string; @@ -43,11 +69,27 @@ export const EventDetails = React.memo( data, id, view, + onEventToggled, onUpdateColumns, onViewSelected, timelineId, toggleColumn, }) => { + const button = useMemo( + () => ( + + + + ), + [onEventToggled] + ); + const tabs: EuiTabbedContentTab[] = [ { id: 'table-view', @@ -73,6 +115,14 @@ export const EventDetails = React.memo( return (
+ + + void; onUpdateColumns: OnUpdateColumns; timelineId: string; toggleColumn: (column: ColumnHeaderOptions) => void; } export const StatefulEventDetails = React.memo( - ({ browserFields, columnHeaders, data, id, onUpdateColumns, timelineId, toggleColumn }) => { + ({ + browserFields, + columnHeaders, + data, + id, + onEventToggled, + onUpdateColumns, + timelineId, + toggleColumn, + }) => { const [view, setView] = useState('table-view'); const handleSetView = useCallback((newView) => setView(newView), []); @@ -34,6 +44,7 @@ export const StatefulEventDetails = React.memo( columnHeaders={columnHeaders} data={data} id={id} + onEventToggled={onEventToggled} onUpdateColumns={onUpdateColumns} onViewSelected={handleSetView} timelineId={timelineId} diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 674eb3325efc2..8c1f69279d31c 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import useResizeObserver from 'use-resize-observer/polyfilled'; +import '../../mock/match_media'; import { mockIndexPattern, TestProviders } from '../../mock'; import { wait } from '../../lib/helpers'; 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 6e6ba4911be26..3f474da102ca4 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 @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiPanel } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { getOr, isEmpty, union } from 'lodash/fp'; import React, { useEffect, useMemo, useState } from 'react'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import deepEqual from 'fast-deep-equal'; import { BrowserFields, DocValueFields } from '../../containers/source'; @@ -34,13 +34,40 @@ import { } from '../../../../../../../src/plugins/data/public'; import { inputsModel } from '../../store'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; +import { ExitFullScreen } from '../exit_full_screen'; +import { useFullScreen } from '../../containers/use_full_screen'; +import { TimelineId } from '../../../../common/types/timeline'; + +export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px +const UTILITY_BAR_HEIGHT = 19; // px +const COMPACT_HEADER_HEIGHT = EVENTS_VIEWER_HEADER_HEIGHT - UTILITY_BAR_HEIGHT; // px + +const UtilityBar = styled.div` + height: ${UTILITY_BAR_HEIGHT}px; +`; + +const TitleText = styled.span` + margin-right: 12px; +`; const DEFAULT_EVENTS_VIEWER_HEIGHT = 500; -const StyledEuiPanel = styled(EuiPanel)` +const StyledEuiPanel = styled(EuiPanel)<{ $isFullScreen: boolean }>` + ${({ $isFullScreen }) => + $isFullScreen && + css` + border: 0; + box-shadow: none; + padding-top: 0; + padding-bottom: 0; + `} max-width: 100%; `; +const TitleFlexGroup = styled(EuiFlexGroup)` + margin-top: 8px; +`; + const EventsContainerLoading = styled.div` width: 100%; overflow: auto; @@ -98,6 +125,7 @@ const EventsViewerComponent: React.FC = ({ utilityBar, graphEventId, }) => { + const { globalFullScreen } = useFullScreen(); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; const kibana = useKibana(); const [isQueryLoading, setIsQueryLoading] = useState(false); @@ -113,6 +141,20 @@ const EventsViewerComponent: React.FC = ({ id, ]); + const justTitle = useMemo(() => {title}, [title]); + + const titleWithExitFullScreen = useMemo( + () => ( + + {justTitle} + + + + + ), + [justTitle] + ); + const combinedQueries = combineQueries({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), dataProviders, @@ -153,7 +195,10 @@ const EventsViewerComponent: React.FC = ({ ); return ( - + {canQueryTimeline ? ( = ({ return ( <> - + {headerFilterGroup} - {utilityBar?.(refetch, totalCountMinusDeleted)} + {utilityBar && ( + {utilityBar?.(refetch, totalCountMinusDeleted)} + )} = ({ excludedRowRendererIds, filters, headerFilterGroup, + height, id, isLive, itemsPerPage, @@ -128,6 +130,7 @@ const StatefulEventsViewerComponent: React.FC = ({ isLoadingIndexPattern={isLoadingIndexPattern} filters={globalFilters} headerFilterGroup={headerFilterGroup} + height={height} indexPattern={indexPatterns} isLive={isLive} itemsPerPage={itemsPerPage!} @@ -203,6 +206,7 @@ type PropsFromRedux = ConnectedProps; export const StatefulEventsViewer = connector( React.memo( StatefulEventsViewerComponent, + // eslint-disable-next-line complexity (prevProps, nextProps) => prevProps.id === nextProps.id && deepEqual(prevProps.columns, nextProps.columns) && @@ -212,6 +216,7 @@ export const StatefulEventsViewer = connector( prevProps.deletedEventIds === nextProps.deletedEventIds && prevProps.end === nextProps.end && deepEqual(prevProps.filters, nextProps.filters) && + prevProps.height === nextProps.height && prevProps.isLive === nextProps.isLive && prevProps.itemsPerPage === nextProps.itemsPerPage && deepEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) && diff --git a/x-pack/plugins/security_solution/public/common/components/exit_full_screen/index.tsx b/x-pack/plugins/security_solution/public/common/components/exit_full_screen/index.tsx new file mode 100644 index 0000000000000..8c5ad95a8de0e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/exit_full_screen/index.tsx @@ -0,0 +1,49 @@ +/* + * 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 { EuiButton, EuiWindowEvent } from '@elastic/eui'; +import React, { useCallback } from 'react'; + +import { useFullScreen } from '../../../common/containers/use_full_screen'; + +import * as i18n from './translations'; + +export const ExitFullScreen: React.FC = () => { + const { globalFullScreen, setGlobalFullScreen } = useFullScreen(); + + const exitFullScreen = useCallback(() => { + setGlobalFullScreen(false); + }, [setGlobalFullScreen]); + + const onKeyDown = useCallback( + (event: KeyboardEvent) => { + if (event.key === 'Escape') { + event.preventDefault(); + + exitFullScreen(); + } + }, + [exitFullScreen] + ); + + if (!globalFullScreen) { + return null; + } + + return ( + <> + + + {i18n.EXIT_FULL_SCREEN} + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/exit_full_screen/translations.ts b/x-pack/plugins/security_solution/public/common/components/exit_full_screen/translations.ts new file mode 100644 index 0000000000000..72d451cfdfc14 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/exit_full_screen/translations.ts @@ -0,0 +1,11 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const EXIT_FULL_SCREEN = i18n.translate('xpack.securitySolution.exitFullScreenButton', { + defaultMessage: 'Exit full screen', +}); diff --git a/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx b/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx index b4d8c790002b2..65901ec589daf 100644 --- a/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx +++ b/x-pack/plugins/security_solution/public/common/components/filters_global/filters_global.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { Sticky } from 'react-sticky'; import styled, { css } from 'styled-components'; +import { FILTERS_GLOBAL_HEIGHT } from '../../../../common/constants'; import { gutterTimeline } from '../../lib/helpers'; const offsetChrome = 49; @@ -17,6 +18,7 @@ const disableSticky = `screen and (max-width: ${euiLightVars.euiBreakpoints.s})` const disableStickyMq = window.matchMedia(disableSticky); const Wrapper = styled.aside<{ isSticky?: boolean }>` + height: ${FILTERS_GLOBAL_HEIGHT}px; position: relative; z-index: ${({ theme }) => theme.eui.euiZNavigation}; background: ${({ theme }) => theme.eui.euiColorEmptyShade}; diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx index ba4f782499802..3a8f2f0c16b96 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx @@ -17,17 +17,19 @@ import { MlPopover } from '../ml_popover/ml_popover'; import { SiemNavigation } from '../navigation'; import * as i18n from './translations'; import { useWithSource } from '../../containers/source'; +import { useFullScreen } from '../../containers/use_full_screen'; import { useGetUrlSearch } from '../navigation/use_get_url_search'; import { useKibana } from '../../lib/kibana'; import { APP_ID, ADD_DATA_PATH, APP_DETECTIONS_PATH } from '../../../../common/constants'; import { LinkAnchor } from '../links'; -const Wrapper = styled.header` - ${({ theme }) => css` +const Wrapper = styled.header<{ show: boolean }>` + ${({ show, theme }) => css` background: ${theme.eui.euiColorEmptyShade}; border-bottom: ${theme.eui.euiBorderThin}; padding: ${theme.eui.paddingSizes.m} ${gutterTimeline} ${theme.eui.paddingSizes.m} ${theme.eui.paddingSizes.l}; + ${show ? '' : 'display: none;'}; `} `; Wrapper.displayName = 'Wrapper'; @@ -42,6 +44,7 @@ interface HeaderGlobalProps { } export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => { const { indicesExist } = useWithSource(); + const { globalFullScreen } = useFullScreen(); const search = useGetUrlSearch(navTabs.overview); const { navigateToApp } = useKibana().services.application; const goToOverview = useCallback( @@ -53,7 +56,7 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine ); return ( - + <> diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/editable_title.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/editable_title.test.tsx index 1e9a2e06474b9..30e992380e7c6 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/editable_title.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/editable_title.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../mock/match_media'; import { TestProviders } from '../../mock'; import { EditableTitle } from './editable_title'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx index 30f510509913a..15711663116f9 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx @@ -8,6 +8,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { shallow } from 'enzyme'; import React from 'react'; +import '../../mock/match_media'; import { TestProviders } from '../../mock'; import { HeaderPage } from './index'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/title.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/title.test.tsx index 5187a32ac9721..fd7a0a5d96e00 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/title.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/title.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../mock/match_media'; import { TestProviders } from '../../mock'; import { Title } from './title'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap index 53b41e2240de2..f2d2d23d60fb1 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`HeaderSection it renders 1`] = ` -
+
diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx index 43245121dd393..f49001bd5d7af 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx @@ -13,12 +13,18 @@ import { Subtitle } from '../subtitle'; interface HeaderProps { border?: boolean; + height?: number; } const Header = styled.header.attrs(() => ({ className: 'siemHeaderSection', }))` - margin-bottom: ${({ theme }) => theme.eui.euiSizeL}; +${({ height }) => + height && + css` + height: ${height}px; + `} + margin-bottom: ${({ height, theme }) => (height ? 0 : theme.eui.euiSizeL)}; user-select: text; ${({ border }) => @@ -32,6 +38,7 @@ Header.displayName = 'Header'; export interface HeaderSectionProps extends HeaderProps { children?: React.ReactNode; + height?: number; id?: string; split?: boolean; subtitle?: string | React.ReactNode; @@ -43,6 +50,7 @@ export interface HeaderSectionProps extends HeaderProps { const HeaderSectionComponent: React.FC = ({ border, children, + height, id, split, subtitle, @@ -50,7 +58,7 @@ const HeaderSectionComponent: React.FC = ({ titleSize = 'm', tooltip, }) => ( -
+
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/entity_draggable.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/entity_draggable.test.tsx index c48a5590b49cf..e9940d088e606 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/entity_draggable.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/entity_draggable.test.tsx @@ -6,6 +6,8 @@ import React from 'react'; import { shallow } from 'enzyme'; + +import '../../mock/match_media'; import { EntityDraggableComponent } from './entity_draggable'; import { TestProviders } from '../../mock/test_providers'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx index f7fa0ac0a8be1..434cbd8ada88e 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx @@ -7,6 +7,8 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; + +import '../../../mock/match_media'; import { AnomalyScoreComponent } from './anomaly_score'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx index d0b923002d6d4..a900c3e49f912 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx @@ -7,6 +7,8 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; + +import '../../../mock/match_media'; import { AnomalyScoresComponent, createJobKey } from './anomaly_scores'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/draggable_score.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/draggable_score.test.tsx index f7759bb74c3ab..673d1a1cdb72e 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/draggable_score.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/draggable_score.test.tsx @@ -5,10 +5,12 @@ */ import React from 'react'; -import { mockAnomalies } from '../mock'; import { cloneDeep } from 'lodash/fp'; import { shallow } from 'enzyme'; + +import '../../../mock/match_media'; import { DraggableScoreComponent } from './draggable_score'; +import { mockAnomalies } from '../mock'; describe('draggable_score', () => { let anomalies = cloneDeep(mockAnomalies); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.test.tsx index b90946c534f3a..d370a901a6262 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_host_table_columns.test.tsx @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; + +import '../../../mock/match_media'; import { getAnomaliesHostTableColumnsCurated } from './get_anomalies_host_table_columns'; import { HostsType } from '../../../../hosts/store/model'; import * as i18n from './translations'; import { AnomaliesByHost, Anomaly } from '../types'; import { Columns } from '../../paginated_table'; import { TestProviders } from '../../../mock'; -import React from 'react'; import { useMountAppended } from '../../../utils/use_mount_appended'; const startDate = new Date(2001).toISOString(); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.test.tsx index 79277c46e1c9d..69a4e383413f2 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/tables/get_anomalies_network_table_columns.test.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../mock/match_media'; import { getAnomaliesNetworkTableColumnsCurated } from './get_anomalies_network_table_columns'; import { NetworkType } from '../../../../network/store/model'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx index f539bb7831c1c..9a5654ed6475f 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx @@ -7,11 +7,13 @@ import { EuiBadge, EuiDescriptionList, EuiFlexGroup, EuiIcon, EuiPage } from '@elastic/eui'; import styled, { createGlobalStyle } from 'styled-components'; +import { FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../common/constants'; + /* SIDE EFFECT: the following `createGlobalStyle` overrides default styling in angular code that was not theme-friendly and `EuiPopover`, `EuiToolTip` global styles */ -export const AppGlobalStyle = createGlobalStyle` +export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimary: string } } }>` /* dirty hack to fix draggables with tooltip on FF */ body#siem-app { position: static; @@ -57,6 +59,10 @@ export const AppGlobalStyle = createGlobalStyle` z-index: 9950; } + /** applies a "toggled" button style to the Full Screen button */ + .${FULL_SCREEN_TOGGLED_CLASS_NAME} { + ${({ theme }) => `background-color: ${theme.eui.euiColorPrimary} !important`}; + } `; export const DescriptionListStyled = styled(EuiDescriptionList)` diff --git a/x-pack/plugins/security_solution/public/common/components/tables/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/tables/helpers.test.tsx index 7ceb34755648e..b28c7e70b8ae8 100644 --- a/x-pack/plugins/security_solution/public/common/components/tables/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/tables/helpers.test.tsx @@ -4,14 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; +import { shallow } from 'enzyme'; + +import '../../mock/match_media'; import { getRowItemDraggables, getRowItemOverflow, getRowItemDraggable, OverflowFieldComponent, } from './helpers'; -import React from 'react'; -import { shallow } from 'enzyme'; import { TestProviders } from '../../mock'; import { getEmptyValue } from '../empty_value'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx index b393e9ae6319b..1e93fdb936728 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx @@ -7,6 +7,7 @@ import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; +import '../../mock/match_media'; import { mockBrowserFields } from '../../containers/source/mock'; import { apolloClientObservable, diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx index e5a1fb6120285..667d1816e8f07 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx @@ -7,6 +7,7 @@ import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; +import '../../mock/match_media'; import { TestProviders, mockIndexPattern } from '../../mock'; import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; diff --git a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx index 3223c5058fa7f..03f9b43678003 100644 --- a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx @@ -5,9 +5,10 @@ */ import classNames from 'classnames'; -import React from 'react'; +import React, { useEffect } from 'react'; import styled from 'styled-components'; +import { useFullScreen } from '../../containers/use_full_screen'; import { gutterTimeline } from '../../lib/helpers'; import { AppGlobalStyle } from '../page/index'; @@ -45,6 +46,11 @@ const WrapperPageComponent: React.FC = ({ style, noPadding, }) => { + const { setGlobalFullScreen } = useFullScreen(); + useEffect(() => { + setGlobalFullScreen(false); // exit full screen mode on page load + }, [setGlobalFullScreen]); + const classes = classNames(className, { siemWrapperPage: true, 'siemWrapperPage--restrictWidthDefault': 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 new file mode 100644 index 0000000000000..b8050034d34a6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx @@ -0,0 +1,39 @@ +/* + * 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 { useCallback, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { inputsSelectors } from '../../store'; +import { inputsActions } from '../../store/actions'; + +export const useFullScreen = () => { + const dispatch = useDispatch(); + const globalFullScreen = useSelector(inputsSelectors.globalFullScreenSelector) ?? false; + const timelineFullScreen = useSelector(inputsSelectors.timelineFullScreenSelector) ?? false; + + const setGlobalFullScreen = useCallback( + (fullScreen: boolean) => dispatch(inputsActions.setFullScreen({ id: 'global', fullScreen })), + [dispatch] + ); + + const setTimelineFullScreen = useCallback( + (fullScreen: boolean) => dispatch(inputsActions.setFullScreen({ id: 'timeline', fullScreen })), + [dispatch] + ); + + const memoizedReturn = useMemo( + () => ({ + globalFullScreen, + setGlobalFullScreen, + setTimelineFullScreen, + timelineFullScreen, + }), + [globalFullScreen, setGlobalFullScreen, setTimelineFullScreen, timelineFullScreen] + ); + + return memoizedReturn; +}; diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts b/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts index efad0638b2971..5d00882f778c0 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts @@ -37,6 +37,11 @@ export const startAutoReload = actionCreator<{ id: InputsModelId }>('START_KQL_A export const stopAutoReload = actionCreator<{ id: InputsModelId }>('STOP_KQL_AUTO_RELOAD'); +export const setFullScreen = actionCreator<{ + id: InputsModelId; + fullScreen: boolean; +}>('SET_FULL_SCREEN'); + export const setQuery = actionCreator<{ inputId: InputsModelId; id: string; diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/helpers.ts b/x-pack/plugins/security_solution/public/common/store/inputs/helpers.ts index 1883f05dc9e9d..82a2072056d9f 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/helpers.ts @@ -9,6 +9,22 @@ import { get } from 'lodash/fp'; import { InputsModel, TimeRange, Refetch, RefetchKql, InspectQuery } from './model'; import { InputsModelId } from './constants'; +export const updateInputFullScreen = ( + inputId: InputsModelId, + fullScreen: boolean, + state: InputsModel +): InputsModel => ({ + ...state, + global: { + ...state.global, + fullScreen: inputId === 'global' ? fullScreen : state.global.fullScreen, + }, + timeline: { + ...state.timeline, + fullScreen: inputId === 'timeline' ? fullScreen : state.timeline.fullScreen, + }, +}); + export const updateInputTimerange = ( inputId: InputsModelId, timerange: TimeRange, diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/model.ts b/x-pack/plugins/security_solution/public/common/store/inputs/model.ts index 358124405c146..a8db48c7b31bb 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/model.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/model.ts @@ -80,6 +80,7 @@ export interface InputsRange { query: Query; filters: Filter[]; savedQuery?: SavedQuery; + fullScreen?: boolean; } export interface LinkTo { diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/reducer.ts b/x-pack/plugins/security_solution/public/common/store/inputs/reducer.ts index 40d9ad777acde..a94f0f6ca24ee 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/reducer.ts @@ -12,6 +12,7 @@ import { deleteAllQuery, setAbsoluteRangeDatePicker, setDuration, + setFullScreen, setInspectionParameter, setQuery, setRelativeRangeDatePicker, @@ -38,6 +39,7 @@ import { removeTimelineLink, addTimelineLink, deleteOneQuery as helperDeleteOneQuery, + updateInputFullScreen, } from './helpers'; import { InputsModel, TimeRange } from './model'; @@ -57,6 +59,7 @@ export const initialInputsState: InputsState = { language: 'kuery', }, filters: [], + fullScreen: false, }, timeline: { timerange: { @@ -71,6 +74,7 @@ export const initialInputsState: InputsState = { language: 'kuery', }, filters: [], + fullScreen: false, }, }; @@ -98,6 +102,7 @@ export const createInitialInputsState = (): InputsState => { language: 'kuery', }, filters: [], + fullScreen: false, }, timeline: { timerange: { @@ -118,6 +123,7 @@ export const createInitialInputsState = (): InputsState => { language: 'kuery', }, filters: [], + fullScreen: false, }, }; }; @@ -163,6 +169,9 @@ export const inputsReducer = reducerWithInitialState(initialInputsState) }; return updateInputTimerange(id, timerange, state); }) + .case(setFullScreen, (state, { id, fullScreen }) => { + return updateInputFullScreen(id, fullScreen, state); + }) .case(deleteAllQuery, (state, { id }) => ({ ...state, [id]: { diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/selectors.ts b/x-pack/plugins/security_solution/public/common/store/inputs/selectors.ts index 0eee5ebbfbf77..9feb2f87d7e08 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/selectors.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/selectors.ts @@ -44,6 +44,13 @@ export const timelineTimeRangeSelector = createSelector( (timeline) => timeline.timerange ); +export const globalFullScreenSelector = createSelector(selectGlobal, (global) => global.fullScreen); + +export const timelineFullScreenSelector = createSelector( + selectTimeline, + (timeline) => timeline.fullScreen +); + export const globalTimeRangeSelector = createSelector(selectGlobal, (global) => global.timerange); export const globalPolicySelector = createSelector(selectGlobal, (global) => global.policy); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/alerts_histogram.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/alerts_histogram.test.tsx index 09883e342f998..692d22b115b48 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/alerts_histogram.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/alerts_histogram.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; +import '../../../common/mock/match_media'; import { AlertsHistogram } from './alerts_histogram'; jest.mock('../../../common/lib/kibana'); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx index 4cbfa59aac582..533f13e6781a6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; +import '../../../common/mock/match_media'; import { AlertsHistogramPanel } from './index'; jest.mock('react-router-dom', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx index cc3a47017a835..d5688d84e9759 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; +import '../../../common/mock/match_media'; import { TimelineId } from '../../../../common/types/timeline'; import { TestProviders } from '../../../common/mock'; import { AlertsTableComponent } from './index'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 405ba0719a910..30cfe2d02354f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -61,6 +61,7 @@ interface OwnProps { timelineId: TimelineIdLiteral; canUserCRUD: boolean; defaultFilters?: Filter[]; + eventsViewerBodyHeight?: number; hasIndexWrite: boolean; from: string; loading: boolean; @@ -86,6 +87,7 @@ export const AlertsTableComponent: React.FC = ({ clearEventsLoading, clearSelected, defaultFilters, + eventsViewerBodyHeight, from, globalFilters, globalQuery, @@ -443,6 +445,7 @@ export const AlertsTableComponent: React.FC = ({ defaultModel={alertsDefaultModel} end={to} headerFilterGroup={headerFilterGroup} + height={eventsViewerBodyHeight} id={timelineId} start={from} utilityBar={utilityBarCallback} diff --git a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.test.tsx index a2685017f86d6..efce1dc026353 100644 --- a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; +import '../../../common/mock/match_media'; import { DetectionEngineHeaderPage } from './index'; describe('detection_engine_header_page', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/all_rules_tables/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/all_rules_tables/index.test.tsx index d841af69a7537..59334b53faa17 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/all_rules_tables/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/all_rules_tables/index.test.tsx @@ -7,6 +7,7 @@ import React, { useRef } from 'react'; import { shallow } from 'enzyme'; +import '../../../../common/mock/match_media'; import { AllRulesTables } from './index'; import { AllRulesTabs } from '../../../pages/detection_engine/rules/all'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx index 89f6399071dd3..a41da908085bc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; +import '../../../../common/mock/match_media'; import { PrePackagedRulesPrompt } from './load_empty_prompt'; jest.mock('react-router-dom', () => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index f4004a66c8f80..e7a8c4854fa9e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -5,15 +5,33 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { useParams } from 'react-router-dom'; import '../../../common/mock/match_media'; +import { + apolloClientObservable, + createSecuritySolutionStorageMock, + kibanaObservable, + mockGlobalState, + TestProviders, + SUB_PLUGINS_REDUCER, +} from '../../../common/mock'; import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { DetectionEnginePageComponent } from './detection_engine'; import { useUserInfo } from '../../components/user_info'; import { useWithSource } from '../../../common/containers/source'; +import { createStore, State } from '../../../common/store'; +import { mockHistory, Router } from '../../../cases/components/__mock__/router'; +// Test will fail because we will to need to mock some core services to make the test work +// For now let's forget about SiemSearchBar and QueryBar +jest.mock('../../../common/components/search_bar', () => ({ + SiemSearchBar: () => null, +})); +jest.mock('../../../common/components/query_bar', () => ({ + QueryBar: () => null, +})); jest.mock('../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../components/user_info'); jest.mock('../../../common/containers/source'); @@ -36,6 +54,19 @@ jest.mock('react-router-dom', () => { }; }); +const state: State = { + ...mockGlobalState, +}; + +const { storage } = createSecuritySolutionStorageMock(); +const store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage +); + describe('DetectionEnginePageComponent', () => { beforeAll(() => { (useParams as jest.Mock).mockReturnValue({}); @@ -47,14 +78,18 @@ describe('DetectionEnginePageComponent', () => { }); it('renders correctly', () => { - const wrapper = shallow( - + const wrapper = mount( + + + + + ); - expect(wrapper.find('FiltersGlobal')).toHaveLength(1); + expect(wrapper.find('FiltersGlobal').exists()).toBe(true); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index aef9f2adcbcc8..acafb15db3448 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -4,12 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiSpacer } from '@elastic/eui'; +import { EuiSpacer, EuiWindowEvent } from '@elastic/eui'; +import { noop } from 'lodash/fp'; import React, { useCallback, useMemo, useState } from 'react'; import { StickyContainer } from 'react-sticky'; import { connect, ConnectedProps } from 'react-redux'; - +import { useWindowSize } from 'react-use'; import { useHistory } from 'react-router-dom'; + +import { globalHeaderHeightPx } from '../../../app/home'; import { SecurityPageName } from '../../../app/types'; import { TimelineId } from '../../../../common/types/timeline'; import { useGlobalTime } from '../../../common/containers/use_global_time'; @@ -31,6 +34,7 @@ import { NoWriteAlertsCallOut } from '../../components/no_write_alerts_callout'; import { AlertsHistogramPanel } from '../../components/alerts_histogram_panel'; import { alertsHistogramOptions } from '../../components/alerts_histogram_panel/config'; import { useUserInfo } from '../../components/user_info'; +import { EVENTS_VIEWER_HEADER_HEIGHT } from '../../../common/components/events_viewer/events_viewer'; import { OverviewEmpty } from '../../../overview/components/overview_empty'; import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; @@ -39,6 +43,14 @@ import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unau import * as i18n from './translations'; import { LinkButton } from '../../../common/components/links'; import { useFormatUrl } from '../../../common/components/link_to'; +import { FILTERS_GLOBAL_HEIGHT } from '../../../../common/constants'; +import { useFullScreen } from '../../../common/containers/use_full_screen'; +import { Display } from '../../../hosts/pages/display'; +import { + getEventsViewerBodyHeight, + MIN_EVENTS_VIEWER_BODY_HEIGHT, +} from '../../../timelines/components/timeline/body/helpers'; +import { footerHeight } from '../../../timelines/components/timeline/footer'; import { buildShowBuildingBlockFilter } from '../../components/alerts_table/default_config'; export const DetectionEnginePageComponent: React.FC = ({ @@ -47,6 +59,8 @@ export const DetectionEnginePageComponent: React.FC = ({ setAbsoluteRangeDatePicker, }) => { const { to, from, deleteQuery, setQuery } = useGlobalTime(); + const { height: windowHeight } = useWindowSize(); + const { globalFullScreen } = useFullScreen(); const { loading: userInfoLoading, isSignalIndexExists, @@ -136,51 +150,66 @@ export const DetectionEnginePageComponent: React.FC = ({ {hasIndexWrite != null && !hasIndexWrite && } {indicesExist ? ( + - - - {i18n.LAST_ALERT} - {': '} - {lastAlerts} - - ) - } - title={i18n.PAGE_TITLE} - > - + + + {i18n.LAST_ALERT} + {': '} + {lastAlerts} + + ) + } + title={i18n.PAGE_TITLE} > - {i18n.BUTTON_MANAGE_RULES} - - + + {i18n.BUTTON_MANAGE_RULES} + + + + + - - ({ + SiemSearchBar: () => null, +})); +jest.mock('../../../../../common/components/query_bar', () => ({ + QueryBar: () => null, +})); jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../components/user_info'); @@ -38,6 +55,18 @@ jest.mock('react-router-dom', () => { }; }); +const state: State = { + ...mockGlobalState, +}; +const { storage } = createSecuritySolutionStorageMock(); +const store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage +); + describe('RuleDetailsPageComponent', () => { beforeAll(() => { (useUserInfo as jest.Mock).mockReturnValue({}); @@ -49,17 +78,21 @@ describe('RuleDetailsPageComponent', () => { }); it('renders correctly', () => { - const wrapper = shallow( - , + const wrapper = mount( + + + + + , { wrappingComponent: TestProviders, } ); - expect(wrapper.find('DetectionEngineHeaderPage')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="header-page-title"]').exists()).toBe(true); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 2e7ef1180f4e3..7eb5c3a535377 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -15,13 +15,17 @@ import { EuiTab, EuiTabs, EuiToolTip, + EuiWindowEvent, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { noop } from 'lodash/fp'; import React, { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'; import { useParams, useHistory } from 'react-router-dom'; import { StickyContainer } from 'react-sticky'; import { connect, ConnectedProps } from 'react-redux'; +import { useWindowSize } from 'react-use'; +import { globalHeaderHeightPx } from '../../../../../app/home'; import { TimelineId } from '../../../../../../common/types/timeline'; import { UpdateDateRange } from '../../../../../common/components/charts/common'; import { FiltersGlobal } from '../../../../../common/components/filters_global'; @@ -62,6 +66,7 @@ import * as ruleI18n from '../translations'; import * as i18n from './translations'; import { useGlobalTime } from '../../../../../common/containers/use_global_time'; import { alertsHistogramOptions } from '../../../../components/alerts_histogram_panel/config'; +import { EVENTS_VIEWER_HEADER_HEIGHT } from '../../../../../common/components/events_viewer/events_viewer'; import { inputsSelectors } from '../../../../../common/store/inputs'; import { State } from '../../../../../common/store'; import { InputsRange } from '../../../../../common/store/inputs/model'; @@ -76,7 +81,15 @@ import { SecurityPageName } from '../../../../../app/types'; import { LinkButton } from '../../../../../common/components/links'; import { useFormatUrl } from '../../../../../common/components/link_to'; import { ExceptionsViewer } from '../../../../../common/components/exceptions/viewer'; +import { FILTERS_GLOBAL_HEIGHT } from '../../../../../../common/constants'; +import { useFullScreen } from '../../../../../common/containers/use_full_screen'; +import { Display } from '../../../../../hosts/pages/display'; import { ExceptionListTypeEnum, ExceptionIdentifiers } from '../../../../../lists_plugin_deps'; +import { + getEventsViewerBodyHeight, + MIN_EVENTS_VIEWER_BODY_HEIGHT, +} from '../../../../../timelines/components/timeline/body/helpers'; +import { footerHeight } from '../../../../../timelines/components/timeline/footer'; enum RuleDetailTabs { alerts = 'alerts', @@ -141,6 +154,8 @@ export const RuleDetailsPageComponent: FC = ({ const mlCapabilities = useMlCapabilities(); const history = useHistory(); const { formatUrl } = useFormatUrl(SecurityPageName.detections); + const { height: windowHeight } = useWindowSize(); + const { globalFullScreen } = useFullScreen(); // TODO: Refactor license check + hasMlAdminPermissions to common check const hasMlPermissions = @@ -329,140 +344,156 @@ export const RuleDetailsPageComponent: FC = ({ {userHasNoPermissions(canUserCRUD) && } {indicesExist ? ( + - - - {detectionI18n.LAST_ALERT} - {': '} - {lastAlerts} - , - ] - : []), - , - ]} - title={title} - > - - - - + + + {detectionI18n.LAST_ALERT} + {': '} + {lastAlerts} + , + ] + : []), + , + ]} + title={title} + > + + + - + > + + + + + + + + + {ruleI18n.EDIT_RULE_SETTINGS} + + + + + + + + + + {ruleError} + + + + - - - - - {ruleI18n.EDIT_RULE_SETTINGS} - + + + + + {defineRuleData != null && ( + + )} + - - + + + + {scheduleRuleData != null && ( + + )} + - - {ruleError} - - - - - - - - - - - {defineRuleData != null && ( - - )} - - - - - - {scheduleRuleData != null && ( - - )} - - - - - - - {tabs} - + + {tabs} + + {ruleDetailTab === RuleDetailTabs.alerts && ( <> - - + + + + {ruleId != null && ( ` + ${({ show }) => (show ? '' : 'display: none;')}; +`; + +Display.displayName = 'Display'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index b37d91cc2be3b..a3885eac5377c 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiSpacer } from '@elastic/eui'; +import { EuiSpacer, EuiWindowEvent } from '@elastic/eui'; +import { noop } from 'lodash/fp'; import React, { useCallback } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { StickyContainer } from 'react-sticky'; @@ -22,6 +23,7 @@ import { manageQuery } from '../../common/components/page/manage_query'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { KpiHostsQuery } from '../containers/kpi_hosts'; +import { useFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { useWithSource } from '../../common/containers/source'; import { LastEventIndexKey } from '../../graphql/types'; @@ -34,6 +36,7 @@ import { SpyRoute } from '../../common/utils/route/spy_routes'; import { esQuery } from '../../../../../../src/plugins/data/public'; import { useMlCapabilities } from '../../common/components/ml_popover/hooks/use_ml_capabilities'; import { OverviewEmpty } from '../../overview/components/overview_empty'; +import { Display } from './display'; import { HostsTabs } from './hosts_tabs'; import { navTabsHosts } from './nav_tabs'; import * as i18n from './translations'; @@ -47,6 +50,7 @@ const KpiHostsComponentManage = manageQuery(KpiHostsComponent); export const HostsComponent = React.memo( ({ filters, query, setAbsoluteRangeDatePicker, hostsPagePath }) => { const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime(); + const { globalFullScreen } = useFullScreen(); const capabilities = useMlCapabilities(); const kibana = useKibana(); const { tabName } = useParams(); @@ -88,44 +92,47 @@ export const HostsComponent = React.memo( <> {indicesExist ? ( + - - } - title={i18n.PAGE_TITLE} - /> - - - {({ kpiHosts, loading, id, inspect, refetch }) => ( - - )} - - - - - - - + + + } + title={i18n.PAGE_TITLE} + /> + + + {({ kpiHosts, loading, id, inspect, refetch }) => ( + + )} + + + + + + + + { const { initializeTimeline } = useManageTimeline(); const dispatch = useDispatch(); - + const { height: windowHeight } = useWindowSize(); + const { globalFullScreen } = useFullScreen(); useEffect(() => { initializeTimeline({ id: TimelineId.hostsPageEvents, @@ -81,19 +93,32 @@ export const EventsQueryTabBody = ({ return ( <> - + {!globalFullScreen && ( + + )} ( capabilitiesFetched, }) => { const { to, from, setQuery, isInitializing } = useGlobalTime(); + const { globalFullScreen } = useFullScreen(); const kibana = useKibana(); const { tabName } = useParams(); @@ -95,56 +99,61 @@ const NetworkComponent = React.memo( <> {indicesExist ? ( + - - } - title={i18n.PAGE_TITLE} - /> - - - - - - - {({ kpiNetwork, loading, id, inspect, refetch }) => ( - - )} - + + + } + title={i18n.PAGE_TITLE} + /> + + + + + + + {({ kpiNetwork, loading, id, inspect, refetch }) => ( + + )} + + {capabilitiesFetched && !isInitializing ? ( <> - + + - + - + + ( ) : ( )} - - ) : ( diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx index 8d004829a34f0..63126da0b9bb5 100644 --- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx @@ -11,6 +11,7 @@ import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; import { ThemeProvider } from 'styled-components'; +import '../../../common/mock/match_media'; import { useQuery } from '../../../common/containers/matrix_histogram'; import { wait } from '../../../common/lib/helpers'; import { mockIndexPattern, TestProviders } from '../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx index c4a941d845f16..8268a550257c9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { OverviewHostProps } from '../overview_host'; import { OverviewNetworkProps } from '../overview_network'; import { mockIndexPattern, TestProviders } from '../../../common/mock'; +import '../../../common/mock/match_media'; import { EventCounts } from '.'; diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx index 8e221445a95d3..fee38ad3c6289 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.test.tsx @@ -6,6 +6,8 @@ import { mount } from 'enzyme'; import React from 'react'; + +import '../../../../common/mock/match_media'; import { TestProviders } from '../../../../common/mock'; import { EndpointOverview } from './index'; diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx index 71cf056f3eb62..6bd0390d014a3 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx @@ -6,6 +6,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../common/mock/match_media'; import { TestProviders } from '../../../common/mock'; import { HostOverview } from './index'; diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx index 5140137ce1b99..30874e8874760 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx @@ -9,6 +9,7 @@ import { mount } from 'enzyme'; import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; +import '../../../common/mock/match_media'; import { apolloClientObservable, mockGlobalState, diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx index d2d823f625690..9ac4f7125f34d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx @@ -8,6 +8,7 @@ import { cloneDeep } from 'lodash/fp'; import { mount } from 'enzyme'; import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; +import '../../../common/mock/match_media'; import { apolloClientObservable, mockGlobalState, diff --git a/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.test.tsx index a5edffc2a099a..b31094b07a829 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/certificate_fingerprint/index.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { TestProviders } from '../../../common/mock'; +import '../../../common/mock/match_media'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { CertificateFingerprint } from '.'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/duration/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/duration/index.test.tsx index 94123000888aa..c38eb23195c06 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/duration/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/duration/index.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import '../../../common/mock/match_media'; import { TestProviders } from '../../../common/mock'; import { ONE_MILLISECOND_AS_NANOSECONDS } from '../formatted_duration/helpers'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx index cf12740d93a18..c3b67e3300459 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { FlowTarget, GetIpOverviewQuery, HostEcsFields } from '../../../graphql/types'; import { TestProviders } from '../../../common/mock'; +import '../../../common/mock/match_media'; import { getEmptyValue } from '../../../common/components/empty_value'; import { diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.test.tsx index 16174e92b3c37..62306046c7b8c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/category.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import '../../../common/mock/match_media'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { Category } from './category'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_browser.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_browser.test.tsx index 7c4e3d435e1ed..9340ee8cf0c7f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_browser.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_browser.test.tsx @@ -7,6 +7,7 @@ import { mount } from 'enzyme'; import React from 'react'; +import '../../../common/mock/match_media'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { TestProviders } from '../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx index e4c9621c2f71c..f4f8adc9f0419 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { TestProviders } from '../../../common/mock'; +import '../../../common/mock/match_media'; import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers'; import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../timeline/body/constants'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx index 1f917c664e813..44e4818830acd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_name.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { TestProviders } from '../../../common/mock'; +import '../../../common/mock/match_media'; import { getColumnsWithTimestamp } from '../../../common/components/event_details/helpers'; import { FieldName } from './field_name'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.test.tsx index b55bbfc023774..c2ddba6bd88c3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/fields_pane.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import '../../../common/mock/match_media'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { TestProviders } from '../../../common/mock'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx index ed3f957ad11a8..a3c7440bece24 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx @@ -7,6 +7,7 @@ import { mount } from 'enzyme'; import React from 'react'; +import '../../../common/mock/match_media'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { TestProviders } from '../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header_with_close_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header_with_close_button/index.test.tsx index 9b7d4c3266c56..cfdca8950d314 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header_with_close_button/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header_with_close_button/index.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { TimelineType } from '../../../../../common/types/timeline'; import { TestProviders } from '../../../../common/mock'; +import '../../../../common/mock/match_media'; import { FlyoutHeaderWithCloseButton } from '.'; jest.mock('react-router-dom', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx index 1616738897b0a..f41d318ba9587 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx @@ -10,11 +10,13 @@ import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import { Resizable, ResizeCallback } from 're-resizable'; -import { TimelineResizeHandle } from './timeline_resize_handle'; import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context'; +import { useFullScreen } from '../../../../common/containers/use_full_screen'; +import { timelineActions } from '../../../store/timeline'; + +import { TimelineResizeHandle } from './timeline_resize_handle'; import * as i18n from './translations'; -import { timelineActions } from '../../../store/timeline'; const minWidthPixels = 550; // do not allow the flyout to shrink below this width (pixels) const maxWidthPercent = 95; // do not allow the flyout to grow past this percentage of the view @@ -44,12 +46,12 @@ const RESIZABLE_ENABLE = { left: true }; const FlyoutPaneComponent: React.FC = ({ children, - flyoutHeight, onClose, timelineId, width, }) => { const dispatch = useDispatch(); + const { timelineFullScreen } = useFullScreen(); const onResizeStop: ResizeCallback = useCallback( (_e, _direction, _ref, delta) => { @@ -80,9 +82,9 @@ const FlyoutPaneComponent: React.FC = ({ ); const resizableHandleComponent = useMemo( () => ({ - left: , + left: , }), - [flyoutHeight] + [] ); return ( @@ -98,8 +100,8 @@ const FlyoutPaneComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/timeline_resize_handle.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/timeline_resize_handle.tsx index 741ed0a09ebf6..7192580f2426d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/timeline_resize_handle.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/timeline_resize_handle.tsx @@ -6,15 +6,17 @@ import styled from 'styled-components'; -export const TIMELINE_RESIZE_HANDLE_WIDTH = 2; // px +export const TIMELINE_RESIZE_HANDLE_WIDTH = 4; // px -export const TimelineResizeHandle = styled.div<{ height: number }>` +export const TimelineResizeHandle = styled.div` + background-color: ${({ theme }) => theme.eui.euiColorLightShade}; cursor: col-resize; - height: 100%; min-height: 20px; - width: 0; - border: ${TIMELINE_RESIZE_HANDLE_WIDTH}px solid ${(props) => props.theme.eui.euiColorLightShade}; + width: ${TIMELINE_RESIZE_HANDLE_WIDTH}px; z-index: 2; - height: ${({ height }) => `${height}px`}; + height: 100vh; position: absolute; + &:hover { + background-color: ${({ theme }) => theme.eui.euiColorPrimary}; + } `; 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 ba2d8fbfa61e5..2559dcab61e13 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 @@ -4,21 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiToolTip, +} from '@elastic/eui'; import { noop } from 'lodash/fp'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { connect, ConnectedProps, useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import { SecurityPageName } from '../../../app/types'; +import { FULL_SCREEN } from '../timeline/body/column_headers/translations'; import { AllCasesModal } from '../../../cases/components/all_cases_modal'; +import { EXIT_FULL_SCREEN } from '../../../common/components/exit_full_screen/translations'; +import { APP_ID, FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../common/constants'; +import { useFullScreen } from '../../../common/containers/use_full_screen'; import { getCaseDetailsUrl, getCreateCaseUrl } from '../../../common/components/link_to'; -import { APP_ID } from '../../../../common/constants'; import { useKibana } from '../../../common/lib/kibana'; import { State } from '../../../common/store'; +import { TimelineId, TimelineType } from '../../../../common/types/timeline'; import { timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../store/timeline/defaults'; import { TimelineModel } from '../../store/timeline/model'; +import { isFullScreen } from '../timeline/body/column_headers'; import { NewCase, ExistingCase } from '../timeline/properties/helpers'; import { UNTITLED_TIMELINE } from '../timeline/properties/translations'; import { @@ -28,7 +40,6 @@ import { import { Resolver } from '../../../resolver/view'; import * as i18n from './translations'; -import { TimelineType } from '../../../../common/types/timeline'; const OverlayContainer = styled.div<{ bodyHeight?: number }>` height: ${({ bodyHeight }) => (bodyHeight ? `${bodyHeight}px` : 'auto')}; @@ -41,6 +52,10 @@ const StyledResolver = styled(Resolver)` height: 100%; `; +const FullScreenButtonIcon = styled(EuiButtonIcon)` + margin: 4px 0 4px 0; +`; + interface OwnProps { bodyHeight?: number; graphEventId?: string; @@ -48,6 +63,46 @@ interface OwnProps { timelineType: TimelineType; } +const Navigation = ({ + fullScreen, + globalFullScreen, + onCloseOverlay, + timelineId, + timelineFullScreen, + toggleFullScreen, +}: { + fullScreen: boolean; + globalFullScreen: boolean; + onCloseOverlay: () => void; + timelineId: string; + timelineFullScreen: boolean; + toggleFullScreen: () => void; +}) => ( + + + + {i18n.BACK_TO_EVENTS} + + + + + + + + +); + const GraphOverlayComponent = ({ bodyHeight, graphEventId, @@ -86,17 +141,45 @@ const GraphOverlayComponent = ({ }, [currentTimeline, dispatch, graphEventId, navigateToApp, onCloseCaseModal, timelineId, title] ); + const { + timelineFullScreen, + setTimelineFullScreen, + globalFullScreen, + setGlobalFullScreen, + } = useFullScreen(); + const fullScreen = useMemo( + () => isFullScreen({ globalFullScreen, timelineId, timelineFullScreen }), + [globalFullScreen, timelineId, timelineFullScreen] + ); + const toggleFullScreen = useCallback(() => { + if (timelineId === TimelineId.active) { + setTimelineFullScreen(!timelineFullScreen); + } else { + setGlobalFullScreen(!globalFullScreen); + } + }, [ + timelineId, + setTimelineFullScreen, + timelineFullScreen, + setGlobalFullScreen, + globalFullScreen, + ]); return ( - - {i18n.BACK_TO_EVENTS} - + - {timelineType === TimelineType.default && ( + {timelineId === TimelineId.active && timelineType === TimelineType.default && ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx index 113c2dca97506..899a6d7486f94 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/ja3_fingerprint/index.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { TestProviders } from '../../../common/mock'; +import '../../../common/mock/match_media'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { Ja3Fingerprint } from '.'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx index 24f8d910b4feb..c2026a71ac6ff 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/netflow/index.test.tsx @@ -10,6 +10,7 @@ import { shallow } from 'enzyme'; import { asArrayIfExists } from '../../../common/lib/helpers'; import { getMockNetflowData } from '../../../common/mock'; +import '../../../common/mock/match_media'; import { TestProviders } from '../../../common/mock/test_providers'; import { TLS_CLIENT_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx index e2def46b936be..e671244d97b57 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx @@ -9,6 +9,7 @@ import { MockedProvider } from 'react-apollo/test-utils'; import React from 'react'; import { wait } from '../../../common/lib/helpers'; +import '../../../common/mock/match_media'; import { TestProviders, apolloClient } from '../../../common/mock/test_providers'; import { mockOpenTimelineQueryResults } from '../../../common/mock/timeline_results'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../pages/timelines_page'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx index f42914c86f46b..57a6431a06b90 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx @@ -10,6 +10,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; +import '../../../common/mock/match_media'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../pages/timelines_page'; import { OpenTimelineResult, OpenTimelineProps } from './types'; import { TimelinesTableProps } from './timelines_table'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx index 1d08f0296ce0d..12df17ceba666 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx @@ -10,6 +10,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; +import '../../../../common/mock/match_media'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines_page'; import { OpenTimelineResult, OpenTimelineProps } from '../types'; import { TimelinesTableProps } from '../timelines_table'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.test.tsx index 9bec06e5ed917..eddfdf6e01df2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.test.tsx @@ -11,6 +11,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; +import '../../../../common/mock/match_media'; import { mockTimelineResults } from '../../../../common/mock/timeline_results'; import { OpenTimelineResult } from '../types'; import { TimelinesTableProps } from '.'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx index 112329ac1738d..b8b2630e09c6e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx @@ -11,6 +11,7 @@ import React from 'react'; import { ThemeProvider } from 'styled-components'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import '../../../../common/mock/match_media'; import { getEmptyValue } from '../../../../common/components/empty_value'; import { OpenTimelineResult } from '../types'; import { mockTimelineResults } from '../../../../common/mock/timeline_results'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.test.tsx index 390ce8c0b6940..0f2b3cdea4eec 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.test.tsx @@ -10,6 +10,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; +import '../../../../common/mock/match_media'; import { getEmptyValue } from '../../../../common/components/empty_value'; import { mockTimelineResults } from '../../../../common/mock/timeline_results'; import { OpenTimelineResult } from '../types'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.test.tsx index f1df605c072dd..6e3f0037003b1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.test.tsx @@ -10,6 +10,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; +import '../../../../common/mock/match_media'; import { mockTimelineResults } from '../../../../common/mock/timeline_results'; import { TimelinesTable, TimelinesTableProps } from '.'; import { OpenTimelineResult } from '../types'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.test.tsx index f230a964c3c2a..649e38865f907 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.test.tsx @@ -10,6 +10,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; +import '../../../../common/mock/match_media'; import { mockTimelineResults } from '../../../../common/mock/timeline_results'; import { OpenTimelineResult } from '../types'; import { TimelinesTable, TimelinesTableProps } from '.'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index a5610cabc1774..13c2b14d26eca 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -1,503 +1,591 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` - - - - - + + + + + + - - - - - - - - - - + ] + } + isSelectAllChecked={false} + onColumnRemoved={[MockFunction]} + onColumnResized={[MockFunction]} + onColumnSorted={[MockFunction]} + onSelectAll={[Function]} + onUpdateColumns={[MockFunction]} + showEventsSelect={false} + showSelectAllCheckbox={false} + sort={ + Object { + "columnId": "fooColumn", + "sortDirection": "desc", + } + } + timelineId="test" + toggleColumn={[MockFunction]} + /> + + + + + + `; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts index 588f407416803..21e135218c871 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts @@ -9,9 +9,10 @@ import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_DATE_COLUMN_MIN_WIDTH, DEFAULT_ACTIONS_COLUMN_WIDTH, + EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH, SHOW_CHECK_BOXES_COLUMN_WIDTH, - MINIMUM_ACTIONS_COLUMN_WIDTH, } from '../constants'; +import '../../../../../common/mock/match_media'; describe('helpers', () => { describe('getColumnWidthFromType', () => { @@ -36,12 +37,12 @@ describe('helpers', () => { }); test('returns the events viewer actions column width when isEventViewer is true', () => { - expect(getActionsColumnWidth(true)).toEqual(MINIMUM_ACTIONS_COLUMN_WIDTH); + expect(getActionsColumnWidth(true)).toEqual(EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH); }); test('returns the events viewer actions column width + checkbox width when isEventViewer is true and showCheckboxes is true', () => { expect(getActionsColumnWidth(true, true)).toEqual( - MINIMUM_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH + EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH ); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx index 6a7734ce3161d..6685ce7d7a018 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../common/mock/match_media'; import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants'; import { defaultHeaders } from './default_headers'; import { Direction } from '../../../../../graphql/types'; @@ -28,22 +29,24 @@ describe('ColumnHeaders', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - + + + ); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx index b139aa1a7a9a6..a3e177604fbd4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiCheckbox } from '@elastic/eui'; +import { EuiButtonIcon, EuiCheckbox, EuiToolTip } from '@elastic/eui'; import { noop } from 'lodash/fp'; import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { Droppable, DraggableChildrenFn } from 'react-beautiful-dnd'; @@ -18,6 +18,10 @@ import { DRAG_TYPE_FIELD, droppableTimelineColumnsPrefix, } from '../../../../../common/components/drag_and_drop/helpers'; +import { EXIT_FULL_SCREEN } from '../../../../../common/components/exit_full_screen/translations'; +import { FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../../../common/constants'; +import { useFullScreen } from '../../../../../common/containers/use_full_screen'; +import { TimelineId } from '../../../../../../common/types/timeline'; import { OnColumnRemoved, OnColumnResized, @@ -42,6 +46,8 @@ import { Sort } from '../sort'; import { EventsSelect } from './events_select'; import { ColumnHeader } from './column_header'; +import * as i18n from './translations'; + interface Props { actionsColumnWidth: number; browserFields: BrowserFields; @@ -81,6 +87,18 @@ export const DraggableContainer = React.memo( DraggableContainer.displayName = 'DraggableContainer'; +export const isFullScreen = ({ + globalFullScreen, + timelineId, + timelineFullScreen, +}: { + globalFullScreen: boolean; + timelineId: string; + timelineFullScreen: boolean; +}) => + (timelineId === TimelineId.active && timelineFullScreen) || + (timelineId !== TimelineId.active && globalFullScreen); + /** Renders the timeline header columns */ export const ColumnHeadersComponent = ({ actionsColumnWidth, @@ -101,6 +119,26 @@ export const ColumnHeadersComponent = ({ toggleColumn, }: Props) => { const [draggingIndex, setDraggingIndex] = useState(null); + const { + timelineFullScreen, + setTimelineFullScreen, + globalFullScreen, + setGlobalFullScreen, + } = useFullScreen(); + + const toggleFullScreen = useCallback(() => { + if (timelineId === TimelineId.active) { + setTimelineFullScreen(!timelineFullScreen); + } else { + setGlobalFullScreen(!globalFullScreen); + } + }, [ + timelineId, + setTimelineFullScreen, + timelineFullScreen, + setGlobalFullScreen, + globalFullScreen, + ]); const handleSelectAllChange = useCallback( (event: React.ChangeEvent) => { @@ -165,6 +203,11 @@ export const ColumnHeadersComponent = ({ ] ); + const fullScreen = useMemo( + () => isFullScreen({ globalFullScreen, timelineId, timelineFullScreen }), + [globalFullScreen, timelineId, timelineFullScreen] + ); + return ( @@ -206,6 +249,25 @@ export const ColumnHeadersComponent = ({ /> + + + + + + + + {showEventsSelect && ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/translations.ts index becdece2c7612..1ebfa957b654f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/translations.ts @@ -18,6 +18,10 @@ export const FIELD = i18n.translate('xpack.securitySolution.timeline.fieldToolti defaultMessage: 'Field', }); +export const FULL_SCREEN = i18n.translate('xpack.securitySolution.timeline.fullScreenButton', { + defaultMessage: 'Full screen', +}); + export const TYPE = i18n.translate('xpack.securitySolution.timeline.typeTooltip', { defaultMessage: 'Type', }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts index 6b6ae3c3467b5..576dedfc28b1b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts @@ -8,12 +8,12 @@ export const MINIMUM_ACTIONS_COLUMN_WIDTH = 50; // px; /** The (fixed) width of the Actions column */ -export const DEFAULT_ACTIONS_COLUMN_WIDTH = 76; // px; +export const DEFAULT_ACTIONS_COLUMN_WIDTH = 24 * 4; // px; /** * The (fixed) width of the Actions column when the timeline body is used as * an events viewer, which has fewer actions than a regular events viewer */ -export const EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH = 26; // px; +export const EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH = 24 * 3; // px; /** Additional column width to include when checkboxes are shown **/ export const SHOW_CHECK_BOXES_COLUMN_WIDTH = 24; // px; /** The default minimum width of a column (when a width for the column type is not specified) */ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx index 07ef165a6d911..28a4bf6d8ac51 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../common/mock/match_media'; import { mockTimelineData } from '../../../../../common/mock'; import { defaultHeaders } from '../column_headers/default_headers'; import { columnRenderers } from '../renderers'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 344fbb59bbe57..3236482e6bc27 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -248,6 +248,7 @@ const StatefulEventComponent: React.FC = ({ event={detailsData || emptyDetails} forceExpand={!!expanded[event._id] && !loading} id={event._id} + onEventToggled={onToggleExpanded} onUpdateColumns={onUpdateColumns} timelineId={timelineId} toggleColumn={toggleColumn} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.ts index 317f1ed20119b..067cea175c99b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.ts @@ -128,3 +128,38 @@ export const getInvestigateInResolverAction = ({ dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: eventId })), width: DEFAULT_ICON_BUTTON_WIDTH, }); + +/** + * The minimum height of a timeline-based events viewer body, as seen in several + * views, e.g. `Detections`, `Events`, `External events`, etc + */ +export const MIN_EVENTS_VIEWER_BODY_HEIGHT = 500; // px + +interface GetEventsViewerBodyHeightParams { + /** the height of the header, e.g. the section containing "`Showing n event / alerts`, and `Open` / `In progress` / `Closed` filters" */ + headerHeight: number; + /** the height of the footer, e.g. "`25 of 100 events / alerts`, `Load More`, `Updated n minutes ago`" */ + footerHeight: number; + /** the height of the global Kibana chrome, common throughout the app */ + kibanaChromeHeight: number; + /** the (combined) height of other non-events viewer content, e.g. the global search / filter bar in full screen mode */ + otherContentHeight: number; + /** the full height of the window */ + windowHeight: number; +} + +export const getEventsViewerBodyHeight = ({ + footerHeight, + headerHeight, + kibanaChromeHeight, + otherContentHeight, + windowHeight, +}: GetEventsViewerBodyHeightParams) => { + if (windowHeight === 0 || !isFinite(windowHeight)) { + return MIN_EVENTS_VIEWER_BODY_HEIGHT; + } + + const combinedHeights = kibanaChromeHeight + otherContentHeight + headerHeight + footerHeight; + + return Math.max(MIN_EVENTS_VIEWER_BODY_HEIGHT, windowHeight - combinedHeights); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 2df6a39f1a3df..b36f1dcc03261 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -7,6 +7,7 @@ import { ReactWrapper } from '@elastic/eui/node_modules/@types/enzyme'; import React from 'react'; import { useSelector } from 'react-redux'; +import '../../../../common/mock/match_media'; import { mockBrowserFields } from '../../../../common/containers/source/mock'; import { Direction } from '../../../../graphql/types'; import { defaultHeaders, mockTimelineData, mockTimelineModel } from '../../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 83e44b77802b7..e971dc6c8e1e2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -29,11 +29,9 @@ import { Events } from './events'; import { ColumnRenderer } from './renderers/column_renderer'; import { RowRenderer } from './renderers/row_renderer'; import { Sort } from './sort'; -import { useManageTimeline } from '../../manage_timeline'; import { GraphOverlay } from '../../graph_overlay'; import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers'; -import { TimelineRowAction } from './actions'; -import { TimelineType } from '../../../../../common/types/timeline'; +import { TimelineId, TimelineType } from '../../../../../common/types/timeline'; export interface BodyProps { addNoteToEvent: AddNoteToEvent; @@ -70,6 +68,11 @@ export interface BodyProps { updateNote: UpdateNote; } +export const hasAdditonalActions = (id: string): boolean => + id === TimelineId.detectionsPage || id === TimelineId.detectionsRulesDetailsPage; + +const EXTRA_WIDTH = 4; // px + /** Renders the timeline body */ export const Body = React.memo( ({ @@ -107,39 +110,14 @@ export const Body = React.memo( updateNote, }) => { const containerElementRef = useRef(null); - const { getManageTimelineById } = useManageTimeline(); - const timelineActions = useMemo( - () => - data.reduce((acc: TimelineRowAction[], rowData) => { - const rowActions = getManageTimelineById(id).timelineRowActions({ - ecsData: rowData.ecs, - nonEcsData: rowData.data, - }); - return rowActions && - rowActions.filter((v) => v.displayType === 'icon').length > - acc.filter((v) => v.displayType === 'icon').length - ? rowActions - : acc; - }, []), - [data, getManageTimelineById, id] - ); - - const additionalActionWidth = useMemo(() => { - let hasContextMenu = false; - return ( - timelineActions.reduce((acc, v) => { - if (v.displayType === 'icon') { - return acc + (v.width ?? 0); - } - const addWidth = hasContextMenu ? 0 : DEFAULT_ICON_BUTTON_WIDTH; - hasContextMenu = true; - return acc + addWidth; - }, 0) ?? 0 - ); - }, [timelineActions]); const actionsColumnWidth = useMemo( - () => getActionsColumnWidth(isEventViewer, showCheckboxes, additionalActionWidth), - [isEventViewer, showCheckboxes, additionalActionWidth] + () => + getActionsColumnWidth( + isEventViewer, + showCheckboxes, + hasAdditonalActions(id) ? DEFAULT_ICON_BUTTON_WIDTH + EXTRA_WIDTH : 0 + ), + [isEventViewer, showCheckboxes, id] ); const columnWidths = useMemo( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.test.tsx index e7e7d1d47f478..d1e8c8aacca47 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/args.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../common/mock/match_media'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; import { TestProviders } from '../../../../../common/mock'; import { ArgsComponent } from './args'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.test.tsx index b4c95d383593a..726273bc90ad8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_details.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { BrowserFields } from '../../../../../../common/containers/source'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.test.tsx index 0990280879a14..750fbc0014464 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_file_details.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { BrowserFields } from '../../../../../../common/containers/source'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx index 41e35427ae254..54af8c89b15d7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock'; import { PrimarySecondaryUserInfo, nilOrUnSet } from './primary_secondary_user_info'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx index d1e67c25bd79c..ef3e2f72d0473 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx @@ -8,6 +8,7 @@ import { EuiFlexItem } from '@elastic/eui'; import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock'; import { SessionUserHostWorkingDir } from './session_user_host_working_dir'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.test.tsx index 0160c62ea40ac..4a0eff1ecf1b2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/bytes/index.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock'; import { PreferenceFormattedBytes } from '../../../../../../common/components/formatted_bytes'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx index ba77709459c28..e2dff4e13b80d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx @@ -11,6 +11,7 @@ import React from 'react'; +import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { mockEndgameDnsRequest } from '../../../../../../common/mock/mock_endgame_ecs_data'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx index 1d46e4c3eb02d..de3eb01612b2a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { TestProviders } from '../../../../../../common/mock'; - +import '../../../../../../common/mock/match_media'; import { DnsRequestEventDetailsLine } from './dns_request_event_details_line'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.test.tsx index 1c7eaef893651..6c9dd5092e7c1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/empty_column_renderer.test.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { TimelineNonEcsData } from '../../../../../graphql/types'; import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../../common/mock'; +import '../../../../../common/mock/match_media'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; import { getEmptyValue } from '../../../../../common/components/empty_value'; import { deleteItemIdx, findItem } from './helpers'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx index e84cb93b87178..47064fa02458a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx @@ -11,6 +11,7 @@ import React from 'react'; +import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx index b2b4b021e5db5..6d4b2b518b582 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx @@ -12,6 +12,7 @@ import React from 'react'; import { TestProviders } from '../../../../../../common/mock'; +import '../../../../../../common/mock/match_media'; import { EndgameSecurityEventDetailsLine } from './endgame_security_event_details_line'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.test.tsx index 4471c26ef8fd7..98a706d5836a0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/exit_code_draggable.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { TestProviders } from '../../../../../common/mock'; +import '../../../../../common/mock/match_media'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; import { ExitCodeDraggable } from './exit_code_draggable'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx index 70e0e74675cd2..a038ceab15b44 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import '../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../common/mock'; import { FileDraggable } from './file_draggable'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.test.tsx index 3e055682d27a4..867cf42146485 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { get } from 'lodash/fp'; import React from 'react'; +import '../../../../../common/mock/match_media'; import { mockTimelineData, TestProviders } from '../../../../../common/mock'; import { getEmptyValue } from '../../../../../common/components/empty_value'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.test.tsx index 12b093bd517c8..d1ed5e86e72e5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_column_renderer.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; +import '../../../../../common/mock/match_media'; import { TimelineNonEcsData } from '../../../../../graphql/types'; import { mockTimelineData } from '../../../../../common/mock'; import { TestProviders } from '../../../../../common/mock/test_providers'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx index 0b3ea0ce6e430..0c7fbd08ba98c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash'; import React from 'react'; +import '../../../../../common/mock/match_media'; import { mockBrowserFields } from '../../../../../common/containers/source/mock'; import { Ecs } from '../../../../../graphql/types'; import { mockTimelineData } from '../../../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.test.tsx index 85a000bbcaf63..2dadbabd0ae16 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_working_dir.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../common/mock/match_media'; import { mockTimelineData, TestProviders } from '../../../../../common/mock'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; import { HostWorkingDir } from './host_working_dir'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx index 5140b9abc60ef..8a8b40198bdba 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { BrowserFields } from '../../../../../../common/containers/source'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { Ecs } from '../../../../../../graphql/types'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/parent_process_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/parent_process_draggable.test.tsx index 0a173f766ae19..86d39da478c6d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/parent_process_draggable.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/parent_process_draggable.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import '../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../common/mock'; import { ParentProcessDraggable } from './parent_process_draggable'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.test.tsx index b7c2cb7032cc2..9199278c57f7a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_column_renderer.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; +import '../../../../../common/mock/match_media'; import { TimelineNonEcsData } from '../../../../../graphql/types'; import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../../common/mock'; import { getEmptyValue } from '../../../../../common/components/empty_value'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.test.tsx index 91ae94940f7f4..7a7715c86b5c5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_draggable.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import { TestProviders } from '../../../../../common/mock'; +import '../../../../../common/mock/match_media'; import { ProcessDraggable, ProcessDraggableWithNonExistentProcess } from './process_draggable'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx index 55cc61edb064e..e46a5abc6a9fd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { TestProviders } from '../../../../../common/mock'; +import '../../../../../common/mock/match_media'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; import { ProcessHash } from './process_hash'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx index 14f147c61fca3..3b9752224e2c1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { mockTimelineData } from '../../../../../../common/mock'; +import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock/test_providers'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; import { SuricataDetails } from './suricata_details'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx index d36d24f41224c..7d700732a6409 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx @@ -11,6 +11,7 @@ import React from 'react'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { Ecs } from '../../../../../../graphql/types'; import { mockTimelineData } from '../../../../../../common/mock'; +import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock/test_providers'; import { suricataRowRenderer } from './suricata_row_renderer'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.test.tsx index a0cad2b059a4b..61e1a28cc7d7d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; import { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/auth_ssh.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/auth_ssh.test.tsx index 4e4e1a0b7bf6f..791ae8aadc69c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/auth_ssh.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/auth_ssh.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { AuthSsh } from './auth_ssh'; describe('AuthSsh', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.test.tsx index 8efd8e1944331..2f2fe2606d132 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_details.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { BrowserFields } from '../../../../../../common/containers/source'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx index 6c7a74d840d01..52c232f377f79 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { BrowserFields } from '../../../../../../common/containers/source'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; +import '../../../../../../common/mock/match_media'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; import { SystemGenericFileDetails, SystemGenericFileLine } from './generic_file_details'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.test.tsx index 56f9452ba40b8..36b69790726e9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/package.test.tsx @@ -7,6 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; import { Package } from './package'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.test.tsx index 7f460d30d709c..d09837e344d7b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_host_working_dir.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import { TestProviders } from '../../../../../common/mock'; +import '../../../../../common/mock/match_media'; import { UserHostWorkingDir } from './user_host_working_dir'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx index 04b0e6e5fcfae..434be7b23aeee 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_details.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; +import '../../../../../../common/mock/match_media'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx index 2eed6aaf20335..23c38f83b89d4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx @@ -11,6 +11,7 @@ import React from 'react'; import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; import { Ecs } from '../../../../../../graphql/types'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; +import '../../../../../../common/mock/match_media'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; import { zeekRowRenderer } from './zeek_row_renderer'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx index a0c5b3a8e8c65..3b1ce431bfc87 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; +import '../../../../../../common/mock/match_media'; import { Ecs } from '../../../../../../graphql/types'; import { mockTimelineData, TestProviders } from '../../../../../../common/mock'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap index 5674c18010f67..ebe6bfcbc2e9a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/__snapshots__/sort_indicator.test.tsx.snap @@ -1,8 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SortIndicator rendering renders correctly against snapshot 1`] = ` - + + + `; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.test.tsx index 1467813eaf154..dcaedb90e7252 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.test.tsx @@ -8,6 +8,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; import { Direction } from '../../../../../graphql/types'; +import * as i18n from '../translations'; import { getDirection, SortIndicator } from './sort_indicator'; @@ -18,13 +19,29 @@ describe('SortIndicator', () => { expect(wrapper).toMatchSnapshot(); }); - test('it renders the sort indicator', () => { + test('it renders the expected sort indicator when direction is ascending', () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="sortIndicator"]').first().prop('type')).toEqual( + 'sortUp' + ); + }); + + test('it renders the expected sort indicator when direction is descending', () => { const wrapper = mount(); expect(wrapper.find('[data-test-subj="sortIndicator"]').first().prop('type')).toEqual( 'sortDown' ); }); + + test('it renders the expected sort indicator when direction is `none`', () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="sortIndicator"]').first().prop('type')).toEqual( + 'empty' + ); + }); }); describe('getDirection', () => { @@ -40,4 +57,28 @@ describe('SortIndicator', () => { expect(getDirection('none')).toEqual(undefined); }); }); + + describe('sort indicator tooltip', () => { + test('it returns the expected tooltip when the direction is ascending', () => { + const wrapper = mount(); + + expect( + wrapper.find('[data-test-subj="sort-indicator-tooltip"]').first().props().content + ).toEqual(i18n.SORTED_ASCENDING); + }); + + test('it returns the expected tooltip when the direction is descending', () => { + const wrapper = mount(); + + expect( + wrapper.find('[data-test-subj="sort-indicator-tooltip"]').first().props().content + ).toEqual(i18n.SORTED_DESCENDING); + }); + + test('it does NOT render a tooltip when sort direction is `none`', () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="sort-indicator-tooltip"]').exists()).toBe(false); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.tsx index c148e2f6c6295..8b842dfa2197e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/sort/sort_indicator.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiIcon } from '@elastic/eui'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; import React from 'react'; import { Direction } from '../../../../../graphql/types'; +import * as i18n from '../translations'; import { SortDirection } from '.'; @@ -37,8 +38,25 @@ interface Props { } /** Renders a sort indicator */ -export const SortIndicator = React.memo(({ sortDirection }) => ( - -)); +export const SortIndicator = React.memo(({ sortDirection }) => { + const direction = getDirection(sortDirection); + + if (direction != null) { + return ( + + + + ); + } else { + return ; + } +}); SortIndicator.displayName = 'SortIndicator'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts index 20467af290b19..c57002023b79d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts @@ -45,6 +45,20 @@ export const PINNED_WITH_NOTES = i18n.translate( } ); +export const SORTED_ASCENDING = i18n.translate( + 'xpack.securitySolution.timeline.body.sort.sortedAscendingTooltip', + { + defaultMessage: 'Sorted ascending', + } +); + +export const SORTED_DESCENDING = i18n.translate( + 'xpack.securitySolution.timeline.body.sort.sortedDescendingTooltip', + { + defaultMessage: 'Sorted descending', + } +); + export const DISABLE_PIN = i18n.translate( 'xpack.securitySolution.timeline.body.pinning.disablePinnnedTooltip', { @@ -66,6 +80,13 @@ export const COLLAPSE = i18n.translate( } ); +export const COLLAPSE_EVENT = i18n.translate( + 'xpack.securitySolution.timeline.body.actions.collapseEventTooltip', + { + defaultMessage: 'Collapse event', + } +); + export const ACTION_INVESTIGATE_IN_RESOLVER = i18n.translate( 'xpack.securitySolution.timeline.body.actions.investigateInResolverTooltip', { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index b08c6afcaf4a6..269cd14b5973c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -34,6 +34,7 @@ interface Props { event: DetailItem[]; forceExpand?: boolean; hideExpandButton?: boolean; + onEventToggled: () => void; onUpdateColumns: OnUpdateColumns; timelineId: string; toggleColumn: (column: ColumnHeaderOptions) => void; @@ -48,6 +49,7 @@ export const ExpandableEvent = React.memo( id, timelineId, toggleColumn, + onEventToggled, onUpdateColumns, }) => ( @@ -59,6 +61,7 @@ export const ExpandableEvent = React.memo( columnHeaders={columnHeaders} data={event} id={id} + onEventToggled={onEventToggled} onUpdateColumns={onUpdateColumns} timelineId={timelineId} toggleColumn={toggleColumn} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx index ce96e4e50dea0..8b75f8b398ac1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx @@ -10,6 +10,7 @@ import { MockedProvider } from 'react-apollo/test-utils'; import { act } from 'react-dom/test-utils'; import useResizeObserver from 'use-resize-observer/polyfilled'; +import '../../../common/mock/match_media'; import { useSignalIndex, ReturnSignalIndex, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx index ce99304c676ee..efb19275336db 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx @@ -16,6 +16,7 @@ import { TestProviders, kibanaObservable, } from '../../../../common/mock'; +import '../../../../common/mock/match_media'; import { createStore, State } from '../../../../common/store'; import { useThrottledResizeObserver } from '../../../../common/components/utils'; import { Properties, showDescriptionThreshold, showNotesThreshold } from '.'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx index 68a3362b721d8..8f548f16cf1d6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx @@ -3,10 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { shallow } from 'enzyme'; import { TimelineType } from '../../../../../common/types/timeline'; +import { TestProviders } from '../../../../common/mock'; import { useCreateTimelineButton } from './use_create_timeline'; jest.mock('react-redux', () => { @@ -20,11 +22,15 @@ jest.mock('react-redux', () => { describe('useCreateTimelineButton', () => { const mockId = 'mockId'; const timelineType = TimelineType.default; + const wrapperContainer: React.FC<{ children?: React.ReactNode }> = ({ children }) => ( + {children} + ); test('return getButton', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useCreateTimelineButton({ timelineId: mockId, timelineType }) + const { result, waitForNextUpdate } = renderHook( + () => useCreateTimelineButton({ timelineId: mockId, timelineType }), + { wrapper: wrapperContainer } ); await waitForNextUpdate(); @@ -34,8 +40,9 @@ describe('useCreateTimelineButton', () => { test('getButton renders correct outline - EuiButton', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useCreateTimelineButton({ timelineId: mockId, timelineType }) + const { result, waitForNextUpdate } = renderHook( + () => useCreateTimelineButton({ timelineId: mockId, timelineType }), + { wrapper: wrapperContainer } ); await waitForNextUpdate(); @@ -47,8 +54,9 @@ describe('useCreateTimelineButton', () => { test('getButton renders correct outline - EuiButtonEmpty', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useCreateTimelineButton({ timelineId: mockId, timelineType }) + const { result, waitForNextUpdate } = renderHook( + () => useCreateTimelineButton({ timelineId: mockId, timelineType }), + { wrapper: wrapperContainer } ); await waitForNextUpdate(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx index fb05b056cdf82..f418491ac4e47 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx @@ -8,7 +8,12 @@ import { useDispatch } from 'react-redux'; import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; import { defaultHeaders } from '../body/column_headers/default_headers'; import { timelineActions } from '../../../store/timeline'; -import { TimelineTypeLiteral, TimelineType } from '../../../../../common/types/timeline'; +import { useFullScreen } from '../../../../common/containers/use_full_screen'; +import { + TimelineId, + TimelineType, + TimelineTypeLiteral, +} from '../../../../../common/types/timeline'; export const useCreateTimelineButton = ({ timelineId, @@ -20,9 +25,14 @@ export const useCreateTimelineButton = ({ closeGearMenu?: () => void; }) => { const dispatch = useDispatch(); + const { timelineFullScreen, setTimelineFullScreen } = useFullScreen(); const createTimeline = useCallback( - ({ id, show }) => + ({ id, show }) => { + if (id === TimelineId.active && timelineFullScreen) { + setTimelineFullScreen(false); + } + dispatch( timelineActions.createTimeline({ id, @@ -30,8 +40,9 @@ export const useCreateTimelineButton = ({ show, timelineType, }) - ), - [dispatch, timelineType] + ); + }, + [dispatch, setTimelineFullScreen, timelineFullScreen, timelineType] ); const handleButtonClick = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx index 58c46af5606f4..555b22eff0c91 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx @@ -13,6 +13,7 @@ import { timelineQuery } from '../../containers/index.gql_query'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { Direction } from '../../../graphql/types'; import { defaultHeaders, mockTimelineData, mockIndexPattern } from '../../../common/mock'; +import '../../../common/mock/match_media'; import { TestProviders } from '../../../common/mock/test_providers'; import { diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx index bd1fac9b05474..1e0e85d4a48d9 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; +import '../../../common/mock/match_media'; import { mockGlobalState, SUB_PLUGINS_REDUCER,