From 702661d34fb26e40869acbe1ca1e88a568901daf Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Wed, 23 Jun 2021 11:00:29 -0400 Subject: [PATCH] Implement new security solution wrapper (#100405) Co-authored-by: cchaos --- src/core/public/rendering/_base.scss | 1 + .../cases/public/components/panel/index.tsx | 2 +- .../security_solution/common/constants.ts | 3 +- .../detection_rules/sorting.spec.ts | 8 +- .../timelines/data_providers.spec.ts | 3 +- .../integration/timelines/pagination.spec.ts | 5 +- .../cypress/screens/timeline.ts | 2 + .../security_solution/public/app/404.tsx | 6 +- .../security_solution/public/app/app.tsx | 26 ++- .../public/app/home/global_header/index.tsx | 76 +++++++ .../public/app/home/home_navigations.tsx | 2 +- .../public/app/home/index.tsx | 71 ++---- .../template_wrapper/bottom_bar/index.tsx | 54 +++++ .../global_kql_header/index.tsx | 28 +++ .../app/home/template_wrapper/index.tsx | 96 ++++++++ .../security_solution/public/app/index.tsx | 9 +- .../security_solution/public/app/routes.tsx | 14 +- .../public/app/{home => }/translations.ts | 0 .../public/cases/components/create/index.tsx | 2 +- .../public/cases/pages/case.tsx | 6 +- .../public/cases/pages/case_details.tsx | 6 +- .../public/cases/pages/configure_cases.tsx | 6 +- .../public/cases/pages/create_case.tsx | 6 +- .../components/callouts/callout_switcher.tsx | 8 +- .../events_viewer/events_viewer.tsx | 1 + .../common/components/events_viewer/index.tsx | 4 +- .../filters_global.test.tsx.snap | 16 +- .../filters_global/filters_global.tsx | 23 +- .../components/header_global/index.test.tsx | 51 ----- .../common/components/header_global/index.tsx | 155 ------------- .../components/header_global/translations.ts | 19 -- .../__snapshots__/index.test.tsx.snap | 28 +-- .../components/header_page/index.test.tsx | 28 +-- .../common/components/header_page/index.tsx | 52 ++--- .../__snapshots__/index.test.tsx.snap | 2 + .../components/item_details_card/index.tsx | 2 +- .../components/ml_popover/ml_popover.tsx | 26 ++- .../components/navigation/index.test.tsx | 10 +- .../common/components/navigation/index.tsx | 166 ++++++++------ .../navigation/tab_navigation/types.ts | 8 +- .../common/components/navigation/types.ts | 27 +-- .../index.test.tsx | 214 ++++++++++++++++++ .../index.tsx | 90 ++++++++ .../use_security_solution_navigation/types.ts | 15 ++ .../use_navigation_items.tsx | 66 ++++++ .../use_primary_navigation.tsx | 68 ++++++ .../public/common/components/page/index.tsx | 121 +--------- .../__snapshots__/index.test.tsx.snap | 9 + .../index.test.tsx | 10 +- .../{wrapper_page => page_wrapper}/index.tsx | 33 +-- .../public/common/components/panel/index.tsx | 2 +- .../common/components/stat_items/index.tsx | 2 +- .../url_state/initialize_redux_by_url.tsx | 1 - .../__snapshots__/index.test.tsx.snap | 9 - .../common/hooks/use_global_header_portal.tsx | 6 +- .../alerts_histogram_panel/index.tsx | 2 +- .../components/alerts_table/index.tsx | 2 +- .../need_admin_for_update_callout/index.tsx | 19 +- .../no_api_integration_callout/index.tsx | 17 +- .../rules/step_about_rule_details/index.tsx | 2 +- .../components/rules/step_panel/index.tsx | 2 +- .../value_lists_management_modal/modal.tsx | 2 +- .../detection_engine/detection_engine.tsx | 18 +- .../detection_engine/rules/create/index.tsx | 15 +- .../rules/details/failure_history.tsx | 4 +- .../detection_engine/rules/details/index.tsx | 10 +- .../detection_engine/rules/edit/index.tsx | 6 +- .../pages/detection_engine/rules/index.tsx | 6 +- .../public/hosts/pages/details/index.tsx | 14 +- .../public/hosts/pages/hosts.test.tsx | 4 +- .../public/hosts/pages/hosts.tsx | 17 +- .../public/management/common/breadcrumbs.ts | 2 +- .../components/administration_list_page.tsx | 12 +- .../pages/policy/view/policy_details.tsx | 12 +- .../__snapshots__/index.test.tsx.snap | 60 ++--- .../__snapshots__/index.test.tsx.snap | 2 +- .../__snapshots__/embeddable.test.tsx.snap | 1 + .../components/embeddables/embeddable.tsx | 4 +- .../public/network/pages/details/index.tsx | 10 +- .../public/network/pages/network.tsx | 15 +- .../components/overview_host/index.tsx | 2 +- .../components/overview_network/index.tsx | 2 +- .../public/overview/pages/overview.tsx | 10 +- .../security_solution/public/plugin.tsx | 2 +- .../public/resolver/view/graph_controls.tsx | 4 +- .../resolver/view/panels/event_detail.tsx | 6 +- .../resolver/view/panels/node_detail.tsx | 6 +- .../resolver/view/panels/node_events.tsx | 4 +- .../view/panels/node_events_of_type.tsx | 4 +- .../public/resolver/view/panels/node_list.tsx | 2 +- .../public/resolver/view/styles.tsx | 5 + .../components/flyout/bottom_bar/index.tsx | 51 +---- .../open_timeline/open_timeline.tsx | 2 +- .../timeline/data_providers/providers.tsx | 1 - .../timelines/components/timeline/styles.tsx | 2 +- .../public/timelines/pages/timelines_page.tsx | 12 +- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 98 files changed, 1213 insertions(+), 868 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/app/home/global_header/index.tsx create mode 100644 x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx create mode 100644 x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx create mode 100644 x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx rename x-pack/plugins/security_solution/public/app/{home => }/translations.ts (100%) delete mode 100644 x-pack/plugins/security_solution/public/common/components/header_global/index.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/header_global/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/header_global/translations.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/types.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/page_wrapper/__snapshots__/index.test.tsx.snap rename x-pack/plugins/security_solution/public/common/components/{wrapper_page => page_wrapper}/index.test.tsx (65%) rename x-pack/plugins/security_solution/public/common/components/{wrapper_page => page_wrapper}/index.tsx (68%) delete mode 100644 x-pack/plugins/security_solution/public/common/components/wrapper_page/__snapshots__/index.test.tsx.snap diff --git a/src/core/public/rendering/_base.scss b/src/core/public/rendering/_base.scss index 4bd6afe90d342..92ba28ff70887 100644 --- a/src/core/public/rendering/_base.scss +++ b/src/core/public/rendering/_base.scss @@ -38,6 +38,7 @@ @mixin kbnAffordForHeader($headerHeight) { @include euiHeaderAffordForFixed($headerHeight); + #securitySolutionStickyKQL, #app-fixed-viewport { top: $headerHeight; } diff --git a/x-pack/plugins/cases/public/components/panel/index.tsx b/x-pack/plugins/cases/public/components/panel/index.tsx index 652d22409cb0c..802fd4c7f44a6 100644 --- a/x-pack/plugins/cases/public/components/panel/index.tsx +++ b/x-pack/plugins/cases/public/components/panel/index.tsx @@ -25,7 +25,7 @@ import { EuiPanel } from '@elastic/eui'; * Ref: https://www.styled-components.com/docs/faqs#why-am-i-getting-html-attribute-warnings * Ref: https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html */ -export const Panel = styled(({ loading, ...props }) => )` +export const Panel = styled(({ loading, ...props }) => )` position: relative; ${({ loading }) => loading && diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index e65ff1afcc9c3..d112630facbc6 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -44,7 +44,8 @@ export const DEFAULT_INTERVAL_VALUE = 300000; // ms export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges'; export const DEFAULT_TRANSFORMS = 'securitySolution:transforms'; export const SCROLLING_DISABLED_CLASS_NAME = 'scrolling-disabled'; -export const GLOBAL_HEADER_HEIGHT = 98; // px +export const GLOBAL_HEADER_HEIGHT = 96; // px +export const GLOBAL_HEADER_HEIGHT_WITH_GLOBAL_BANNER = 128; // px 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'; diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts index f1ee0d39f545f..bf5c281a43e39 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts @@ -129,7 +129,13 @@ describe('Alerts detection rules', () => { }); it('Auto refreshes rules', () => { - cy.clock(Date.now()); + /** + * Ran into the error: timer created with setInterval() but cleared with cancelAnimationFrame() + * There are no cancelAnimationFrames in the codebase that are used to clear a setInterval so + * explicitly set the below overrides. see https://docs.cypress.io/api/commands/clock#Function-names + */ + + cy.clock(Date.now(), ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'Date']); goToManageAlertsDetectionRules(); waitForRulesTableToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts index d42632a66eb26..a0e7e77f89b67 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts @@ -12,6 +12,7 @@ import { TIMELINE_DATA_PROVIDERS_ACTION_MENU, IS_DRAGGING_DATA_PROVIDERS, TIMELINE_FLYOUT_HEADER, + TIMELINE_BOTTOM_BAR_CONTAINER, } from '../../screens/timeline'; import { HOSTS_NAMES_DRAGGABLE } from '../../screens/hosts/all_hosts'; @@ -46,7 +47,7 @@ describe('timeline data providers', () => { it('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => { dragAndDropFirstHostToTimeline(); openTimelineUsingToggle(); - cy.get(TIMELINE_DROPPED_DATA_PROVIDERS) + cy.get(`${TIMELINE_BOTTOM_BAR_CONTAINER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`) .first() .invoke('text') .then((dataProviderText) => { diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts index 568fb90568fb3..8b65f99eb04b8 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/pagination.spec.ts @@ -6,6 +6,7 @@ */ import { + TIMELINE_BOTTOM_BAR_CONTAINER, TIMELINE_EVENT, TIMELINE_EVENTS_COUNT_NEXT_PAGE, TIMELINE_EVENTS_COUNT_PER_PAGE, @@ -50,10 +51,10 @@ describe('Pagination', () => { it('should be able to go to next / previous page', () => { cy.intercept('POST', '/internal/bsearch').as('refetch'); - cy.get(TIMELINE_EVENTS_COUNT_NEXT_PAGE).first().click(); + cy.get(`${TIMELINE_BOTTOM_BAR_CONTAINER} ${TIMELINE_EVENTS_COUNT_NEXT_PAGE}`).first().click(); cy.wait('@refetch').its('response.statusCode').should('eq', 200); - cy.get(TIMELINE_EVENTS_COUNT_PREV_PAGE).first().click(); + cy.get(`${TIMELINE_BOTTOM_BAR_CONTAINER} ${TIMELINE_EVENTS_COUNT_PREV_PAGE}`).first().click(); cy.wait('@refetch').its('response.statusCode').should('eq', 200); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 0a9e5b44feb1f..25cd2357fe02b 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -143,6 +143,8 @@ export const TIMELINE_CORRELATION_TAB = '[data-test-subj="timelineTabs-eql"]'; export const IS_DRAGGING_DATA_PROVIDERS = '.is-dragging'; +export const TIMELINE_BOTTOM_BAR_CONTAINER = '[data-test-subj="timeline-bottom-bar-container"]'; + export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]'; export const TIMELINE_DATA_PROVIDERS_ACTION_MENU = '[data-test-subj="providerActions"]'; diff --git a/x-pack/plugins/security_solution/public/app/404.tsx b/x-pack/plugins/security_solution/public/app/404.tsx index c21f7a4d4d578..2634ffd47bff1 100644 --- a/x-pack/plugins/security_solution/public/app/404.tsx +++ b/x-pack/plugins/security_solution/public/app/404.tsx @@ -8,15 +8,15 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { WrapperPage } from '../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper'; export const NotFoundPage = React.memo(() => ( - + - + )); NotFoundPage.displayName = 'NotFoundPage'; diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx index 2dc7f632c8482..c223570c77201 100644 --- a/x-pack/plugins/security_solution/public/app/app.tsx +++ b/x-pack/plugins/security_solution/public/app/app.tsx @@ -11,7 +11,7 @@ import { Store, Action } from 'redux'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { EuiErrorBoundary } from '@elastic/eui'; -import { AppLeaveHandler } from '../../../../../src/core/public'; +import { AppLeaveHandler, AppMountParameters } from '../../../../../src/core/public'; import { ManageUserInfo } from '../detections/components/user_info'; import { DEFAULT_DARK_MODE, APP_NAME } from '../../common/constants'; @@ -30,10 +30,17 @@ interface StartAppComponent { children: React.ReactNode; history: History; onAppLeave: (handler: AppLeaveHandler) => void; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; store: Store; } -const StartAppComponent: FC = ({ children, history, onAppLeave, store }) => { +const StartAppComponent: FC = ({ + children, + history, + setHeaderActionMenu, + onAppLeave, + store, +}) => { const { i18n } = useKibana().services; const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); @@ -46,7 +53,11 @@ const StartAppComponent: FC = ({ children, history, onAppLeav - + {children} @@ -69,6 +80,7 @@ interface SecurityAppComponentProps { history: History; onAppLeave: (handler: AppLeaveHandler) => void; services: StartServices; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; store: Store; } @@ -77,6 +89,7 @@ const SecurityAppComponent: React.FC = ({ history, onAppLeave, services, + setHeaderActionMenu, store, }) => ( = ({ ...services, }} > - + {children} diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx new file mode 100644 index 0000000000000..98ff11423ce01 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiHeaderSection, + EuiHeaderLinks, + EuiHeaderLink, + EuiHeaderSectionItem, +} from '@elastic/eui'; +import React, { useEffect, useMemo } from 'react'; +import { createPortalNode, OutPortal, InPortal } from 'react-reverse-portal'; +import { i18n } from '@kbn/i18n'; + +import { AppMountParameters } from '../../../../../../../src/core/public'; +import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; +import { MlPopover } from '../../../common/components/ml_popover/ml_popover'; +import { useKibana } from '../../../common/lib/kibana'; +import { ADD_DATA_PATH, APP_DETECTIONS_PATH } from '../../../../common/constants'; + +const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.buttonAddData', { + defaultMessage: 'Add data', +}); + +/** + * This component uses the reverse portal to add the Add Data and ML job settings buttons on the + * right hand side of the Kibana global header + */ +export const GlobalHeader = React.memo( + ({ setHeaderActionMenu }: { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'] }) => { + const portalNode = useMemo(() => createPortalNode(), []); + const { http } = useKibana().services; + + useEffect(() => { + let unmount = () => {}; + + setHeaderActionMenu((element) => { + const mount = toMountPoint(); + unmount = mount(element); + return unmount; + }); + + return () => { + portalNode.unmount(); + unmount(); + }; + }, [portalNode, setHeaderActionMenu]); + + return ( + + + {window.location.pathname.includes(APP_DETECTIONS_PATH) && ( + + + + )} + + + + {BUTTON_ADD_DATA} + + + + + + ); + } +); +GlobalHeader.displayName = 'GlobalHeader'; diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx index 7ebcc96753836..8358e2f9377b8 100644 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import * as i18n from './translations'; +import * as i18n from '../translations'; import { SecurityPageName } from '../types'; import { SiemNavTab } from '../../common/components/navigation/types'; import { 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 1b0ddcfb9ae7d..9a57ab3fc3a73 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -5,57 +5,35 @@ * 2.0. */ -import React, { useEffect, useRef, useState } from 'react'; -import styled from 'styled-components'; +import React, { useRef } from 'react'; -import { TimelineId } from '../../../common/types/timeline'; import { DragDropContextWrapper } from '../../common/components/drag_and_drop/drag_drop_context_wrapper'; -import { Flyout } from '../../timelines/components/flyout'; +import { AppLeaveHandler, AppMountParameters } from '../../../../../../src/core/public'; import { SecuritySolutionAppWrapper } from '../../common/components/page'; -import { HeaderGlobal } from '../../common/components/header_global'; import { HelpMenu } from '../../common/components/help_menu'; -import { AutoSaveWarningMsg } from '../../timelines/components/timeline/auto_save_warning'; import { UseUrlState } from '../../common/components/url_state'; -import { useShowTimeline } from '../../common/utils/timeline/use_show_timeline'; import { navTabs } from './home_navigations'; import { useInitSourcerer, useSourcererScope } from '../../common/containers/sourcerer'; import { useKibana } from '../../common/lib/kibana'; import { DETECTIONS_SUB_PLUGIN_ID } from '../../../common/constants'; import { SourcererScopeName } from '../../common/store/sourcerer/model'; import { useUpgradeEndpointPackage } from '../../common/hooks/endpoint/upgrade'; -import { useThrottledResizeObserver } from '../../common/components/utils'; -import { AppLeaveHandler } from '../../../../../../src/core/public'; - -const Main = styled.main.attrs<{ paddingTop: number }>(({ paddingTop }) => ({ - style: { - paddingTop: `${paddingTop}px`, - }, -}))<{ paddingTop: number }>` - overflow: auto; - display: flex; - flex-direction: column; - flex: 1 1 auto; -`; - -Main.displayName = 'Main'; +import { GlobalHeader } from './global_header'; +import { SecuritySolutionTemplateWrapper } from './template_wrapper'; interface HomePageProps { children: React.ReactNode; onAppLeave: (handler: AppLeaveHandler) => void; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; } -const HomePageComponent: React.FC = ({ children, onAppLeave }) => { - const { application, overlays } = useKibana().services; +const HomePageComponent: React.FC = ({ + children, + onAppLeave, + setHeaderActionMenu, +}) => { + const { application } = useKibana().services; const subPluginId = useRef(''); - const { ref, height = 0 } = useThrottledResizeObserver(300); - const banners$ = overlays.banners.get$(); - const [headerFixed, setHeaderFixed] = useState(true); - const mainPaddingTop = headerFixed ? height : 0; - - useEffect(() => { - const subscription = banners$.subscribe((banners) => setHeaderFixed(!banners.length)); - return () => subscription.unsubscribe(); - }, [banners$]); // Only un/re-subscribe if the Observable changes application.currentAppId$.subscribe((appId) => { subPluginId.current = appId ?? ''; @@ -66,13 +44,13 @@ const HomePageComponent: React.FC = ({ children, onAppLeave }) => ? SourcererScopeName.detections : SourcererScopeName.default ); - const [showTimeline] = useShowTimeline(); - const { browserFields, indexPattern, indicesExist } = useSourcererScope( + const { browserFields, indexPattern } = useSourcererScope( subPluginId.current === DETECTIONS_SUB_PLUGIN_ID ? SourcererScopeName.detections : SourcererScopeName.default ); + // side effect: this will attempt to upgrade the endpoint package if it is not up to date // this will run when a user navigates to the Security Solution app and when they navigate between // tabs in the app. This is useful for keeping the endpoint package as up to date as possible until @@ -81,23 +59,14 @@ const HomePageComponent: React.FC = ({ children, onAppLeave }) => useUpgradeEndpointPackage(); return ( - - - -
- - - {indicesExist && showTimeline && ( - <> - - - - )} - + + + + + {children} - -
- + +
); diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx new file mode 100644 index 0000000000000..08ebbeaee55d4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable react/display-name */ + +import React, { useRef } from 'react'; +import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public'; +import { AppLeaveHandler } from '../../../../../../../../src/core/public'; +import { useKibana } from '../../../../common/lib/kibana'; +import { useShowTimeline } from '../../../../common/utils/timeline/use_show_timeline'; +import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { DETECTIONS_SUB_PLUGIN_ID } from '../../../../../common/constants'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { TimelineId } from '../../../../../common/types/timeline'; +import { AutoSaveWarningMsg } from '../../../../timelines/components/timeline/auto_save_warning'; +import { Flyout } from '../../../../timelines/components/flyout'; + +export const BOTTOM_BAR_CLASSNAME = 'timeline-bottom-bar'; + +export const SecuritySolutionBottomBar = React.memo( + ({ onAppLeave }: { onAppLeave: (handler: AppLeaveHandler) => void }) => { + const subPluginId = useRef(''); + const { application } = useKibana().services; + application.currentAppId$.subscribe((appId) => { + subPluginId.current = appId ?? ''; + }); + + const [showTimeline] = useShowTimeline(); + + const { indicesExist } = useSourcererScope( + subPluginId.current === DETECTIONS_SUB_PLUGIN_ID + ? SourcererScopeName.detections + : SourcererScopeName.default + ); + + return indicesExist && showTimeline ? ( + <> + + + + ) : null; + } +); + +export const SecuritySolutionBottomBarProps: KibanaPageTemplateProps['bottomBarProps'] = { + className: BOTTOM_BAR_CLASSNAME, + 'data-test-subj': 'timeline-bottom-bar-container', + position: 'fixed', + usePortal: false, +}; diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx new file mode 100644 index 0000000000000..3e3c91133eab6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/global_kql_header/index.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import styled from 'styled-components'; +import { OutPortal } from 'react-reverse-portal'; +import { useGlobalHeaderPortal } from '../../../../common/hooks/use_global_header_portal'; + +const StyledStickyWrapper = styled.div` + position: sticky; + z-index: ${(props) => props.theme.eui.euiZLevel2}; + // TOP location is declared in src/public/rendering/_base.scss to keep in line with Kibana Chrome +`; + +export const GlobalKQLHeader = React.memo(() => { + const { globalKQLHeaderPortalNode } = useGlobalHeaderPortal(); + + return ( + + + + ); +}); + +GlobalKQLHeader.displayName = 'GlobalKQLHeader'; diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx new file mode 100644 index 0000000000000..02fd07151f111 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import styled from 'styled-components'; +import { EuiPanel } from '@elastic/eui'; +import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid'; +import { AppLeaveHandler } from '../../../../../../../src/core/public'; +import { KibanaPageTemplate } from '../../../../../../../src/plugins/kibana_react/public'; +import { useSecuritySolutionNavigation } from '../../../common/components/navigation/use_security_solution_navigation'; +import { TimelineId } from '../../../../common/types/timeline'; +import { getTimelineShowStatusByIdSelector } from '../../../timelines/components/flyout/selectors'; +import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; +import { GlobalKQLHeader } from './global_kql_header'; +import { + BOTTOM_BAR_CLASSNAME, + SecuritySolutionBottomBar, + SecuritySolutionBottomBarProps, +} from './bottom_bar'; +import { useShowTimeline } from '../../../common/utils/timeline/use_show_timeline'; +import { gutterTimeline } from '../../../common/lib/helpers'; + +/* eslint-disable react/display-name */ + +/** + * Need to apply the styles via a className to effect the containing bottom bar + * rather than applying them to the timeline bar directly + */ +const StyledKibanaPageTemplate = styled(KibanaPageTemplate)<{ + $isShowingTimelineOverlay?: boolean; + $isTimelineBottomBarVisible?: boolean; +}>` + .${BOTTOM_BAR_CLASSNAME} { + animation: 'none !important'; // disable the default bottom bar slide animation + background: ${({ theme }) => + theme.eui.euiColorEmptyShade}; // Override bottom bar black background + color: inherit; // Necessary to override the bottom bar 'white text' + transform: ${( + { $isShowingTimelineOverlay } // Since the bottom bar wraps the whole overlay now, need to override any transforms when it is open + ) => ($isShowingTimelineOverlay ? 'none' : 'translateY(calc(100% - 50px))')}; + z-index: ${({ theme }) => theme.eui.euiZLevel8}; + + .${IS_DRAGGING_CLASS_NAME} & { + // When a drag is in process the bottom flyout should slide up to allow a drop + transform: none; + } + } + + // If the bottom bar is visible add padding to the navigation + ${({ $isTimelineBottomBarVisible }) => + $isTimelineBottomBarVisible && + ` + @media (min-width: 768px) { + .kbnPageTemplateSolutionNav { + padding-bottom: ${gutterTimeline}; + } + } + `} +`; + +interface SecuritySolutionPageWrapperProps { + onAppLeave: (handler: AppLeaveHandler) => void; +} + +export const SecuritySolutionTemplateWrapper: React.FC = React.memo( + ({ children, onAppLeave }) => { + const solutionNav = useSecuritySolutionNavigation(); + const [isTimelineBottomBarVisible] = useShowTimeline(); + const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []); + const { show: isShowingTimelineOverlay } = useDeepEqualSelector((state) => + getTimelineShowStatus(state, TimelineId.active) + ); + + return ( + } + paddingSize="none" + solutionNav={solutionNav} + restrictWidth={false} + template="default" + > + + + {children} + + + ); + } +); diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx index 1e304c2686960..194f119e35478 100644 --- a/x-pack/plugins/security_solution/public/app/index.tsx +++ b/x-pack/plugins/security_solution/public/app/index.tsx @@ -15,12 +15,19 @@ export const renderApp = ({ element, history, onAppLeave, + setHeaderActionMenu, services, store, SubPluginRoutes, }: RenderAppProps): (() => void) => { render( - + , element diff --git a/x-pack/plugins/security_solution/public/app/routes.tsx b/x-pack/plugins/security_solution/public/app/routes.tsx index 6454653af5214..a9a94a6998286 100644 --- a/x-pack/plugins/security_solution/public/app/routes.tsx +++ b/x-pack/plugins/security_solution/public/app/routes.tsx @@ -10,7 +10,7 @@ import React, { FC, memo, useEffect } from 'react'; import { Route, Router, Switch } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { AppLeaveHandler } from '../../../../../src/core/public'; +import { AppLeaveHandler, AppMountParameters } from '../../../../../src/core/public'; import { ManageRoutesSpy } from '../common/utils/route/manage_spy_routes'; import { RouteCapture } from '../common/components/endpoint/route_capture'; import { AppAction } from '../common/store/actions'; @@ -21,9 +21,15 @@ interface RouterProps { children: React.ReactNode; history: History; onAppLeave: (handler: AppLeaveHandler) => void; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; } -const PageRouterComponent: FC = ({ children, history, onAppLeave }) => { +const PageRouterComponent: FC = ({ + children, + history, + onAppLeave, + setHeaderActionMenu, +}) => { const dispatch = useDispatch<(action: AppAction) => void>(); useEffect(() => { return () => { @@ -42,7 +48,9 @@ const PageRouterComponent: FC = ({ children, history, onAppLeave }) - {children} + + {children} + diff --git a/x-pack/plugins/security_solution/public/app/home/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/app/home/translations.ts rename to x-pack/plugins/security_solution/public/app/translations.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index 91fb45de04320..dfd53ae5cc0b0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -38,7 +38,7 @@ export const Create = React.memo(() => { ); return ( - + {cases.getCreateCase({ onCancel: handleSetIsCancel, onSuccess, diff --git a/x-pack/plugins/security_solution/public/cases/pages/case.tsx b/x-pack/plugins/security_solution/public/cases/pages/case.tsx index 647647afbe0a4..ad0176bda6905 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/case.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/case.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { WrapperPage } from '../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useGetUserCasesPermissions } from '../../common/lib/kibana'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { AllCases } from '../components/all_cases'; @@ -20,9 +20,9 @@ export const CasesPage = React.memo(() => { return userPermissions == null || userPermissions?.read ? ( <> - + - + ) : ( diff --git a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx index a086409e55df5..f6bb27b7b7104 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx @@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom'; import { SecurityPageName } from '../../app/types'; import { SpyRoute } from '../../common/utils/route/spy_routes'; -import { WrapperPage } from '../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useGetUrlSearch } from '../../common/components/navigation/use_get_url_search'; import { useGetUserCasesPermissions, useKibana } from '../../common/lib/kibana'; import { getCaseUrl } from '../../common/components/link_to'; @@ -37,13 +37,13 @@ export const CaseDetailsPage = React.memo(() => { return caseId != null ? ( <> - + - + ) : null; diff --git a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx index c942065e45278..d3f235a5da7dc 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components'; import { SecurityPageName } from '../../app/types'; import { getCaseUrl } from '../../common/components/link_to'; import { useGetUrlSearch } from '../../common/components/navigation/use_get_url_search'; -import { WrapperPage } from '../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useGetUserCasesPermissions, useKibana } from '../../common/lib/kibana'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { navTabs } from '../../app/home/home_navigations'; @@ -51,7 +51,7 @@ const ConfigureCasesPageComponent: React.FC = () => { return ( <> - + @@ -63,7 +63,7 @@ const ConfigureCasesPageComponent: React.FC = () => { owner: [APP_ID], })} - + ); diff --git a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx index 3c5197f19eff1..6c88c4afb6395 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx @@ -10,7 +10,7 @@ import React, { useEffect, useMemo } from 'react'; import { SecurityPageName } from '../../app/types'; import { getCaseUrl } from '../../common/components/link_to'; import { useGetUrlSearch } from '../../common/components/navigation/use_get_url_search'; -import { WrapperPage } from '../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useGetUserCasesPermissions, useKibana } from '../../common/lib/kibana'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { navTabs } from '../../app/home/home_navigations'; @@ -45,10 +45,10 @@ export const CreateCasePage = React.memo(() => { return ( <> - + - + ); diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout_switcher.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout_switcher.tsx index e700bb97e9893..43f10604d8582 100644 --- a/x-pack/plugins/security_solution/public/common/components/callouts/callout_switcher.tsx +++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout_switcher.tsx @@ -6,6 +6,7 @@ */ import React, { FC, memo } from 'react'; +import { EuiSpacer } from '@elastic/eui'; import { CallOutMessage } from './callout_types'; import { CallOut } from './callout'; @@ -21,7 +22,12 @@ const CallOutSwitcherComponent: FC = ({ namespace, conditi const { isVisible, dismiss } = useCallOutStorage([message], namespace); const shouldRender = condition && isVisible(message); - return shouldRender ? : null; + return shouldRender ? ( + <> + + + + ) : null; }; export const CallOutSwitcher = memo(CallOutSwitcherComponent); 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 8326cdaaaf995..5dadd740ae3bc 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 @@ -286,6 +286,7 @@ const EventsViewerComponent: React.FC = ({ {canQueryTimeline ? ( diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index c0a75bdd3edd2..32aa716d4bce3 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -27,10 +27,8 @@ import { useKibana } from '../../lib/kibana'; import { defaultControlColumn } from '../../../timelines/components/timeline/body/control_columns'; import { EventsViewer } from './events_viewer'; -const DEFAULT_EVENTS_VIEWER_HEIGHT = 652; - const FullScreenContainer = styled.div<{ $isFullScreen: boolean }>` - height: ${({ $isFullScreen }) => ($isFullScreen ? '100%' : `${DEFAULT_EVENTS_VIEWER_HEIGHT}px`)}; + height: ${({ $isFullScreen }) => ($isFullScreen ? '100%' : undefined)}; flex: 1 1 auto; display: flex; width: 100%; diff --git a/x-pack/plugins/security_solution/public/common/components/filters_global/__snapshots__/filters_global.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/filters_global/__snapshots__/filters_global.test.tsx.snap index 994e98d8619a1..51326d54a6161 100644 --- a/x-pack/plugins/security_solution/public/common/components/filters_global/__snapshots__/filters_global.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/filters_global/__snapshots__/filters_global.test.tsx.snap @@ -4,17 +4,19 @@ exports[`rendering renders correctly 1`] = ` } > - -

Additional filters here.

-
-
+ +
`; 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 c6b5b6ccde5cd..79c08e50451f7 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 @@ -8,18 +8,9 @@ import React from 'react'; import styled from 'styled-components'; import { InPortal } from 'react-reverse-portal'; - +import { EuiPanel } from '@elastic/eui'; import { useGlobalHeaderPortal } from '../../hooks/use_global_header_portal'; -const Wrapper = styled.aside` - position: relative; - z-index: ${({ theme }) => theme.eui.euiZNavigation}; - background: ${({ theme }) => theme.eui.euiColorEmptyShade}; - border-bottom: ${({ theme }) => theme.eui.euiBorderThin}; - padding: ${({ theme }) => theme.eui.paddingSizes.m} ${({ theme }) => theme.eui.paddingSizes.l}; -`; -Wrapper.displayName = 'Wrapper'; - const FiltersGlobalContainer = styled.header<{ show: boolean }>` display: ${({ show }) => (show ? 'block' : 'none')}; `; @@ -32,13 +23,15 @@ export interface FiltersGlobalProps { } export const FiltersGlobal = React.memo(({ children, show = true }) => { - const { globalHeaderPortalNode } = useGlobalHeaderPortal(); + const { globalKQLHeaderPortalNode } = useGlobalHeaderPortal(); return ( - - - {children} - + + + + {children} + + ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_global/index.test.tsx deleted file mode 100644 index 96a7eacb7fb08..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/header_global/index.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { mount } from 'enzyme'; - -import { useGetUserCasesPermissions } from '../../../common/lib/kibana'; -import { TestProviders } from '../../../common/mock'; -import { HeaderGlobal } from '.'; - -jest.mock('../../../common/lib/kibana'); - -describe('HeaderGlobal', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('does not display the cases tab when the user does not have read permissions', () => { - (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ - crud: false, - read: false, - }); - - const wrapper = mount( - - - - ); - - expect(wrapper.find(`[data-test-subj="navigation-case"]`).exists()).toBeFalsy(); - }); - - it('displays the cases tab when the user has read permissions', () => { - (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ - crud: true, - read: true, - }); - - const wrapper = mount( - - - - ); - - expect(wrapper.find(`[data-test-subj="navigation-case"]`).exists()).toBeTruthy(); - }); -}); 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 deleted file mode 100644 index e91905183aab1..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; -import { pickBy } from 'lodash/fp'; -import React, { forwardRef, useCallback, useMemo } from 'react'; -import styled from 'styled-components'; -import { OutPortal } from 'react-reverse-portal'; - -import { navTabs } from '../../../app/home/home_navigations'; -import { useGlobalFullScreen, useTimelineFullScreen } from '../../containers/use_full_screen'; -import { SecurityPageName } from '../../../app/types'; -import { getAppOverviewUrl } from '../link_to'; -import { MlPopover } from '../ml_popover/ml_popover'; -import { SiemNavigation } from '../navigation'; -import * as i18n from './translations'; -import { useGetUrlSearch } from '../navigation/use_get_url_search'; -import { useGetUserCasesPermissions, useKibana } from '../../lib/kibana'; -import { APP_ID, ADD_DATA_PATH, APP_DETECTIONS_PATH } from '../../../../common/constants'; -import { useGlobalHeaderPortal } from '../../hooks/use_global_header_portal'; -import { LinkAnchor } from '../links'; - -const Wrapper = styled.header<{ $isFixed: boolean }>` - ${({ theme, $isFixed }) => ` - background: ${theme.eui.euiColorEmptyShade}; - border-bottom: ${theme.eui.euiBorderThin}; - width: 100%; - z-index: ${theme.eui.euiZNavigation}; - position: ${$isFixed ? 'fixed' : 'relative'}; - `} -`; -Wrapper.displayName = 'Wrapper'; - -const WrapperContent = styled.div<{ $globalFullScreen: boolean }>` - display: ${({ $globalFullScreen }) => ($globalFullScreen ? 'none' : 'block')}; - padding-top: ${({ $globalFullScreen, theme }) => - $globalFullScreen ? theme.eui.paddingSizes.s : theme.eui.paddingSizes.m}; -`; - -WrapperContent.displayName = 'WrapperContent'; - -const FlexItem = styled(EuiFlexItem)` - min-width: 0; -`; -FlexItem.displayName = 'FlexItem'; - -const FlexGroup = styled(EuiFlexGroup)<{ $hasSibling: boolean }>` - ${({ $hasSibling, theme }) => ` - border-bottom: ${theme.eui.euiBorderThin}; - margin-bottom: 1px; - padding-bottom: 4px; - padding-left: ${theme.eui.paddingSizes.l}; - padding-right: ${theme.eui.paddingSizes.l}; - ${$hasSibling ? `border-bottom: ${theme.eui.euiBorderThin};` : 'border-bottom-width: 0px;'} - `} -`; -FlexGroup.displayName = 'FlexGroup'; - -interface HeaderGlobalProps { - hideDetectionEngine?: boolean; - isFixed?: boolean; -} - -export const HeaderGlobal = React.memo( - forwardRef( - ({ hideDetectionEngine = false, isFixed = true }, ref) => { - const { globalHeaderPortalNode } = useGlobalHeaderPortal(); - const { globalFullScreen } = useGlobalFullScreen(); - const { timelineFullScreen } = useTimelineFullScreen(); - const search = useGetUrlSearch(navTabs.overview); - const { application, http } = useKibana().services; - const { navigateToApp, getUrlForApp } = application; - const overviewPath = useMemo( - () => getUrlForApp(APP_ID, { path: SecurityPageName.overview }), - [getUrlForApp] - ); - const overviewHref = useMemo(() => getAppOverviewUrl(overviewPath, search), [ - overviewPath, - search, - ]); - - const basePath = http.basePath.get(); - const goToOverview = useCallback( - (ev) => { - ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.overview}`, { path: search }); - }, - [navigateToApp, search] - ); - - const hasCasesReadPermissions = useGetUserCasesPermissions()?.read; - - // build a list of tabs to exclude - const tabsToExclude = new Set([ - ...(hideDetectionEngine ? [SecurityPageName.detections] : []), - ...(!hasCasesReadPermissions ? [SecurityPageName.case] : []), - ]); - - // include the tab if it is not in the set of excluded ones - const tabsToDisplay = pickBy((_, key) => !tabsToExclude.has(key), navTabs); - - return ( - - - - - - - - - - - - - - - - - - - {window.location.pathname.includes(APP_DETECTIONS_PATH) && ( - - - - )} - - - - {i18n.BUTTON_ADD_DATA} - - - - - - - - - ); - } - ) -); -HeaderGlobal.displayName = 'HeaderGlobal'; diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/translations.ts b/x-pack/plugins/security_solution/public/common/components/header_global/translations.ts deleted file mode 100644 index a2a22dfe31eb9..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/header_global/translations.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const SECURITY_SOLUTION = i18n.translate( - 'xpack.securitySolution.headerGlobal.securitySolution', - { - defaultMessage: 'Security solution', - } -); - -export const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.headerGlobal.buttonAddData', { - defaultMessage: 'Add data', -}); diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap index 84c8971e3d352..9cb9f28612b15 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap @@ -1,14 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`HeaderPage it renders 1`] = ` -
- + - + - - +

Test supplement

-
-
- + + + -
+ `; 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 78bac02585b9f..8a1748de582c4 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 @@ -57,7 +57,7 @@ describe('HeaderPage', () => { ); - expect(wrapper.find('.siemHeaderPage__linkBack').first().exists()).toBe(true); + expect(wrapper.find('.securitySolutionHeaderPage__linkBack').first().exists()).toBe(true); }); test('it DOES NOT render the back link when not provided', () => { @@ -67,7 +67,7 @@ describe('HeaderPage', () => { ); - expect(wrapper.find('.siemHeaderPage__linkBack').first().exists()).toBe(false); + expect(wrapper.find('.securitySolutionHeaderPage__linkBack').first().exists()).toBe(false); }); test('it renders the first subtitle when provided', () => { @@ -134,27 +134,21 @@ describe('HeaderPage', () => { expect(wrapper.find('[data-test-subj="header-page-supplements"]').first().exists()).toBe(false); }); - test('it applies border styles when border is true', () => { - const wrapper = mount( - - - - ); - const siemHeaderPage = wrapper.find('.siemHeaderPage').first(); - - expect(siemHeaderPage).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(siemHeaderPage).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); - }); - test('it DOES NOT apply border styles when border is false', () => { const wrapper = mount( ); - const siemHeaderPage = wrapper.find('.siemHeaderPage').first(); + const securitySolutionHeaderPage = wrapper.find('.securitySolutionHeaderPage').first(); - expect(siemHeaderPage).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(siemHeaderPage).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); + expect(securitySolutionHeaderPage).not.toHaveStyleRule( + 'border-bottom', + euiDarkVars.euiBorderThin + ); + expect(securitySolutionHeaderPage).not.toHaveStyleRule( + 'padding-bottom', + euiDarkVars.paddingSizes.l + ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx index d01869bb6999b..1c87d70c0c7cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx @@ -5,7 +5,13 @@ * 2.0. */ -import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiProgress } from '@elastic/eui'; +import { + EuiBadge, + EuiProgress, + EuiPageHeader, + EuiPageHeaderSection, + EuiSpacer, +} from '@elastic/eui'; import React, { useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import styled, { css } from 'styled-components'; @@ -25,36 +31,16 @@ interface HeaderProps { } const Header = styled.header.attrs({ - className: 'siemHeaderPage', + className: 'securitySolutionHeaderPage', })` ${({ border, theme }) => css` margin-bottom: ${theme.eui.euiSizeL}; - - ${border && - css` - border-bottom: ${theme.eui.euiBorderThin}; - padding-bottom: ${theme.eui.paddingSizes.l}; - .euiProgress { - top: ${theme.eui.paddingSizes.l}; - } - `} `} `; Header.displayName = 'Header'; -const FlexItem = styled(EuiFlexItem)` - ${({ theme }) => css` - display: block; - - @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { - max-width: 50%; - } - `} -`; -FlexItem.displayName = 'FlexItem'; - const LinkBack = styled.div.attrs({ - className: 'siemHeaderPage__linkBack', + className: 'securitySolutionHeaderPage__linkBack', })` ${({ theme }) => css` font-size: ${theme.eui.euiFontSizeXS}; @@ -117,9 +103,9 @@ const HeaderPageComponent: React.FC = ({ [backOptions, history] ); return ( -
- - + <> + + {backOptions && ( = ({ {subtitle && } {subtitle2 && } {border && isLoading && } - + {children && ( - + {children} - + )} - - {!hideSourcerer && } -
+ {!hideSourcerer && } + + {/* Manually add a 'padding-bottom' to header */} + + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap index c7841f6d6bbcc..f0fd8427140df 100644 --- a/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap @@ -14,6 +14,7 @@ exports[`item_details_card ItemDetailsAction should render correctly 1`] = ` exports[`item_details_card ItemDetailsCard should render correctly with actions 1`] = ` ( ); return ( - + diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx index 561805217e8a1..cc6ac5355f90b 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx @@ -5,7 +5,13 @@ * 2.0. */ -import { EuiButtonEmpty, EuiCallOut, EuiPopover, EuiPopoverTitle, EuiSpacer } from '@elastic/eui'; +import { + EuiHeaderSectionItemButton, + EuiCallOut, + EuiPopover, + EuiPopoverTitle, + EuiSpacer, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; import React, { Dispatch, useCallback, useReducer, useState } from 'react'; @@ -115,14 +121,19 @@ export const MlPopover = React.memo(() => { anchorPosition="downRight" id="integrations-popover" button={ - setIsPopoverOpen(!isPopoverOpen)} + textProps={{ style: { fontSize: '1rem' } }} > {i18n.ML_JOB_SETTINGS} - + } isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(!isPopoverOpen)} @@ -138,7 +149,11 @@ export const MlPopover = React.memo(() => { anchorPosition="downRight" id="integrations-popover" button={ - { setIsPopoverOpen(!isPopoverOpen); dispatch({ type: 'refresh' }); }} + textProps={{ style: { fontSize: '1rem' } }} > {i18n.ML_JOB_SETTINGS} - + } isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(!isPopoverOpen)} diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx index 27db326dddec5..c75b38e03acb4 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx @@ -9,12 +9,12 @@ import { mount } from 'enzyme'; import React from 'react'; import { CONSTANTS } from '../url_state/constants'; -import { SiemNavigationComponent } from './'; +import { TabNavigationComponent } from './'; import { setBreadcrumbs } from './breadcrumbs'; import { navTabs } from '../../../app/home/home_navigations'; import { HostsTableType } from '../../../hosts/store/model'; import { RouteSpyState } from '../../utils/route/types'; -import { SiemNavigationProps, SiemNavigationComponentProps } from './types'; +import { TabNavigationComponentProps, SecuritySolutionTabNavigationProps } from './types'; import { TimelineTabs } from '../../../../common/types/timeline'; jest.mock('react-router-dom', () => { @@ -48,7 +48,9 @@ jest.mock('../../lib/kibana', () => { jest.mock('../link_to'); describe('SIEM Navigation', () => { - const mockProps: SiemNavigationComponentProps & SiemNavigationProps & RouteSpyState = { + const mockProps: TabNavigationComponentProps & + SecuritySolutionTabNavigationProps & + RouteSpyState = { pageName: 'hosts', pathName: '/', detailName: undefined, @@ -89,7 +91,7 @@ describe('SIEM Navigation', () => { }, }, }; - const wrapper = mount(); + const wrapper = mount(); test('it calls setBreadcrumbs with correct path on mount', () => { expect(setBreadcrumbs).toHaveBeenNthCalledWith( 1, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx index 7ea0b26ae8b3b..233b4b2cb1d02 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx @@ -16,75 +16,93 @@ import { useRouteSpy } from '../../utils/route/use_route_spy'; import { makeMapStateToProps } from '../url_state/helpers'; import { setBreadcrumbs } from './breadcrumbs'; import { TabNavigation } from './tab_navigation'; -import { SiemNavigationProps, SiemNavigationComponentProps } from './types'; +import { TabNavigationComponentProps, SecuritySolutionTabNavigationProps } from './types'; -export const SiemNavigationComponent: React.FC< - SiemNavigationComponentProps & SiemNavigationProps & RouteSpyState -> = ({ - detailName, - display, - navTabs, - pageName, - pathName, - search, - tabName, - urlState, - flowTarget, - state, -}) => { - const { - chrome, - application: { getUrlForApp }, - } = useKibana().services; +/** + * @description - This component handels all of the tab navigation seen within a Security Soluton application page, not the Security Solution primary side navigation + * For the primary side nav see './use_security_solution_navigation' + */ +export const TabNavigationComponent: React.FC< + RouteSpyState & SecuritySolutionTabNavigationProps & TabNavigationComponentProps +> = React.memo( + ({ + detailName, + display, + flowTarget, + navTabs, + pageName, + pathName, + search, + state, + tabName, + urlState, + }) => { + const { + chrome, + application: { getUrlForApp }, + } = useKibana().services; - useEffect(() => { - if (pathName || pageName) { - setBreadcrumbs( - { - detailName, - filters: urlState.filters, - flowTarget, - navTabs, - pageName, - pathName, - query: urlState.query, - savedQuery: urlState.savedQuery, - search, - sourcerer: urlState.sourcerer, - state, - tabName, - timeline: urlState.timeline, - timerange: urlState.timerange, - }, - chrome, - getUrlForApp - ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [chrome, pageName, pathName, search, navTabs, urlState, state]); + useEffect(() => { + if (pathName || pageName) { + setBreadcrumbs( + { + detailName, + filters: urlState.filters, + flowTarget, + navTabs, + pageName, + pathName, + query: urlState.query, + savedQuery: urlState.savedQuery, + search, + sourcerer: urlState.sourcerer, + state, + tabName, + timeline: urlState.timeline, + timerange: urlState.timerange, + }, + chrome, + getUrlForApp + ); + } + }, [ + chrome, + pageName, + pathName, + search, + navTabs, + urlState, + state, + detailName, + flowTarget, + tabName, + getUrlForApp, + ]); - return ( - - ); -}; + return ( + + ); + } +); +TabNavigationComponent.displayName = 'TabNavigationComponent'; -export const SiemNavigationRedux = compose< - React.ComponentClass +export const SecuritySolutionTabNavigationRedux = compose< + React.ComponentClass >(connect(makeMapStateToProps))( React.memo( - SiemNavigationComponent, + TabNavigationComponent, (prevProps, nextProps) => prevProps.pathName === nextProps.pathName && prevProps.search === nextProps.search && @@ -94,16 +112,16 @@ export const SiemNavigationRedux = compose< ) ); -const SiemNavigationContainer: React.FC = (props) => { - const [routeProps] = useRouteSpy(); - const stateNavReduxProps: RouteSpyState & SiemNavigationProps = { - ...routeProps, - ...props, - }; - - return ; -}; +export const SecuritySolutionTabNavigation: React.FC = React.memo( + (props) => { + const [routeProps] = useRouteSpy(); + const stateNavReduxProps: RouteSpyState & SecuritySolutionTabNavigationProps = { + ...routeProps, + ...props, + }; -export const SiemNavigation = React.memo(SiemNavigationContainer, (prevProps, nextProps) => - deepEqual(prevProps.navTabs, nextProps.navTabs) + return ; + }, + (prevProps, nextProps) => deepEqual(prevProps.navTabs, nextProps.navTabs) ); +SecuritySolutionTabNavigation.displayName = 'SecuritySolutionTabNavigation'; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts index 4253d08d1ed19..53565d79e6948 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts @@ -7,17 +7,17 @@ import { UrlInputsModel } from '../../../store/inputs/model'; import { CONSTANTS } from '../../url_state/constants'; -import { HostsTableType } from '../../../../hosts/store/model'; import { SourcererScopePatterns } from '../../../store/sourcerer/model'; import { TimelineUrl } from '../../../../timelines/store/timeline/model'; import { Filter, Query } from '../../../../../../../../src/plugins/data/public'; -import { SiemNavigationProps } from '../types'; +import { SecuritySolutionTabNavigationProps } from '../types'; +import { SiemRouteType } from '../../../utils/route/types'; -export interface TabNavigationProps extends SiemNavigationProps { +export interface TabNavigationProps extends SecuritySolutionTabNavigationProps { pathName: string; pageName: string; - tabName: HostsTableType | undefined; + tabName: SiemRouteType | undefined; [CONSTANTS.appQuery]?: Query; [CONSTANTS.filters]?: Filter[]; [CONSTANTS.savedQuery]?: string; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index 9700afcb8cd59..1c317700b1d15 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -5,31 +5,20 @@ * 2.0. */ -import { Filter, Query } from '../../../../../../../src/plugins/data/public'; -import { HostsTableType } from '../../../hosts/store/model'; -import { UrlInputsModel } from '../../store/inputs/model'; -import { TimelineUrl } from '../../../timelines/store/timeline/model'; -import { CONSTANTS, UrlStateType } from '../url_state/constants'; +import { UrlStateType } from '../url_state/constants'; import { SecurityPageName } from '../../../app/types'; -import { SourcererScopePatterns } from '../../store/sourcerer/model'; +import { UrlState } from '../url_state/types'; +import { SiemRouteType } from '../../utils/route/types'; -export interface SiemNavigationProps { +export interface SecuritySolutionTabNavigationProps { display?: 'default' | 'condensed'; navTabs: Record; } - -export interface SiemNavigationComponentProps { - pathName: string; +export interface TabNavigationComponentProps { pageName: string; - tabName: HostsTableType | undefined; - urlState: { - [CONSTANTS.appQuery]?: Query; - [CONSTANTS.filters]?: Filter[]; - [CONSTANTS.savedQuery]?: string; - [CONSTANTS.sourcerer]: SourcererScopePatterns; - [CONSTANTS.timerange]: UrlInputsModel; - [CONSTANTS.timeline]: TimelineUrl; - }; + tabName: SiemRouteType | undefined; + urlState: UrlState; + pathName: string; } export type SearchNavTab = NavTab | { urlKey: UrlStateType; isDetailPage: boolean }; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx new file mode 100644 index 0000000000000..48d3cfb5abcc1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -0,0 +1,214 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public'; +import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana'; +import { SecurityPageName } from '../../../../app/types'; +import { useSecuritySolutionNavigation } from '.'; +import { CONSTANTS } from '../../url_state/constants'; +import { TimelineTabs } from '../../../../../common/types/timeline'; +import { useDeepEqualSelector } from '../../../hooks/use_selector'; +import { UrlInputsModel } from '../../../store/inputs/model'; +import { useRouteSpy } from '../../../utils/route/use_route_spy'; + +jest.mock('../../../lib/kibana'); +jest.mock('../../../hooks/use_selector'); +jest.mock('../../../utils/route/use_route_spy'); + +describe('useSecuritySolutionNavigation', () => { + const mockUrlState = { + [CONSTANTS.appQuery]: { query: 'host.name:"security-solution-es"', language: 'kuery' }, + [CONSTANTS.savedQuery]: '', + [CONSTANTS.sourcerer]: {}, + [CONSTANTS.timeline]: { + activeTab: TimelineTabs.query, + id: '', + isOpen: false, + graphEventId: '', + }, + [CONSTANTS.timerange]: { + global: { + [CONSTANTS.timerange]: { + from: '2020-07-07T08:20:18.966Z', + fromStr: 'now-24h', + kind: 'relative', + to: '2020-07-08T08:20:18.966Z', + toStr: 'now', + }, + linkTo: ['timeline'], + }, + timeline: { + [CONSTANTS.timerange]: { + from: '2020-07-07T08:20:18.966Z', + fromStr: 'now-24h', + kind: 'relative', + to: '2020-07-08T08:20:18.966Z', + toStr: 'now', + }, + linkTo: ['global'], + }, + } as UrlInputsModel, + }; + + const mockRouteSpy = [ + { + detailName: '', + flowTarget: '', + pathName: '', + search: '', + state: '', + tabName: '', + pageName: SecurityPageName.hosts, + }, + ]; + + beforeEach(() => { + (useDeepEqualSelector as jest.Mock).mockReturnValue({ urlState: mockUrlState }); + (useRouteSpy as jest.Mock).mockReturnValue(mockRouteSpy); + (useKibana as jest.Mock).mockReturnValue({ + services: { + application: { + navigateToApp: jest.fn(), + getUrlForApp: (appId: string, options?: { path?: string; absolute?: boolean }) => + `${appId}${options?.path ?? ''}`, + }, + chrome: { + setBreadcrumbs: jest.fn(), + }, + }, + }); + }); + + it('should create navigation config', async () => { + const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(() => + useSecuritySolutionNavigation() + ); + + expect(result.current).toMatchInlineSnapshot(` + Object { + "icon": "logoSecurity", + "items": Array [ + Object { + "id": "securitySolution", + "items": Array [ + Object { + "data-href": "securitySolution:overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-overview", + "disabled": false, + "href": "securitySolution:overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "overview", + "isSelected": false, + "name": "Overview", + "onClick": [Function], + }, + Object { + "data-href": "securitySolution:detections?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-detections", + "disabled": false, + "href": "securitySolution:detections?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "detections", + "isSelected": false, + "name": "Detections", + "onClick": [Function], + }, + Object { + "data-href": "securitySolution:hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-hosts", + "disabled": false, + "href": "securitySolution:hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "hosts", + "isSelected": true, + "name": "Hosts", + "onClick": [Function], + }, + Object { + "data-href": "securitySolution:network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-network", + "disabled": false, + "href": "securitySolution:network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "network", + "isSelected": false, + "name": "Network", + "onClick": [Function], + }, + Object { + "data-href": "securitySolution:timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-timelines", + "disabled": false, + "href": "securitySolution:timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "timelines", + "isSelected": false, + "name": "Timelines", + "onClick": [Function], + }, + Object { + "data-href": "securitySolution:administration", + "data-test-subj": "navigation-administration", + "disabled": false, + "href": "securitySolution:administration", + "id": "administration", + "isSelected": false, + "name": "Administration", + "onClick": [Function], + }, + ], + "name": "", + }, + ], + "name": "Security", + } + `); + }); + + describe('Permission gated routes', () => { + describe('cases', () => { + it('should display the cases navigation item when the user has read permissions', () => { + (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ + crud: true, + read: true, + }); + + const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(() => + useSecuritySolutionNavigation() + ); + + const caseNavItem = result.current?.items[0].items?.find( + (item) => item['data-test-subj'] === 'navigation-case' + ); + expect(caseNavItem).toMatchInlineSnapshot(` + Object { + "data-href": "securitySolution:case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-case", + "disabled": false, + "href": "securitySolution:case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "case", + "isSelected": false, + "name": "Cases", + "onClick": [Function], + } + `); + }); + + it('should not display the cases navigation item when the user does not have read permissions', () => { + (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ + crud: false, + read: false, + }); + + const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(() => + useSecuritySolutionNavigation() + ); + + const caseNavItem = result.current?.items[0].items?.find( + (item) => item['data-test-subj'] === 'navigation-case' + ); + expect(caseNavItem).toBeFalsy(); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx new file mode 100644 index 0000000000000..f2aee86912dd7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect } from 'react'; +import { pickBy } from 'lodash/fp'; +import { usePrimaryNavigation } from './use_primary_navigation'; +import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana'; +import { setBreadcrumbs } from '../breadcrumbs'; +import { makeMapStateToProps } from '../../url_state/helpers'; +import { useRouteSpy } from '../../../utils/route/use_route_spy'; +import { navTabs } from '../../../../app/home/home_navigations'; +import { useDeepEqualSelector } from '../../../hooks/use_selector'; +import { SecurityPageName } from '../../../../../common/constants'; + +/** + * @description - This hook provides the structure necessary by the KibanaPageTemplate for rendering the primary security_solution side navigation. + * TODO: Consolidate & re-use the logic in the hooks in this directory that are replicated from the tab_navigation to maintain breadcrumbs, telemetry, etc... + */ +export const useSecuritySolutionNavigation = () => { + const [routeProps] = useRouteSpy(); + const urlMapState = makeMapStateToProps(); + const { urlState } = useDeepEqualSelector(urlMapState); + const { + chrome, + application: { getUrlForApp }, + } = useKibana().services; + + const { detailName, flowTarget, pageName, pathName, search, state, tabName } = routeProps; + + useEffect(() => { + if (pathName || pageName) { + setBreadcrumbs( + { + detailName, + filters: urlState.filters, + flowTarget, + navTabs, + pageName, + pathName, + query: urlState.query, + savedQuery: urlState.savedQuery, + search, + sourcerer: urlState.sourcerer, + state, + tabName, + timeline: urlState.timeline, + timerange: urlState.timerange, + }, + chrome, + getUrlForApp + ); + } + }, [ + chrome, + pageName, + pathName, + search, + urlState, + state, + detailName, + flowTarget, + tabName, + getUrlForApp, + ]); + + const hasCasesReadPermissions = useGetUserCasesPermissions()?.read; + + // build a list of tabs to exclude + const tabsToExclude = new Set([ + ...(!hasCasesReadPermissions ? [SecurityPageName.case] : []), + ]); + + // include the tab if it is not in the set of excluded ones + const tabsToDisplay = pickBy((_, key) => !tabsToExclude.has(key), navTabs); + + return usePrimaryNavigation({ + query: urlState.query, + filters: urlState.filters, + navTabs: tabsToDisplay, + pageName, + sourcerer: urlState.sourcerer, + savedQuery: urlState.savedQuery, + timeline: urlState.timeline, + timerange: urlState.timerange, + }); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/types.ts new file mode 100644 index 0000000000000..f639b8a37f0da --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/types.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TabNavigationProps } from '../tab_navigation/types'; + +export type PrimaryNavigationItemsProps = Omit< + TabNavigationProps, + 'pathName' | 'pageName' | 'tabName' +> & { selectedTabId: string }; + +export type PrimaryNavigationProps = Omit; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx new file mode 100644 index 0000000000000..42ca7f4c65460 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { APP_ID } from '../../../../../common/constants'; +import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/telemetry'; +import { getSearch } from '../helpers'; +import { PrimaryNavigationItemsProps } from './types'; +import { useKibana } from '../../../lib/kibana'; + +export const usePrimaryNavigationItems = ({ + filters, + navTabs, + query, + savedQuery, + selectedTabId, + sourcerer, + timeline, + timerange, +}: PrimaryNavigationItemsProps) => { + const { navigateToApp, getUrlForApp } = useKibana().services.application; + + const navItems = Object.values(navTabs).map((tab) => { + const { id, name, disabled } = tab; + const isSelected = selectedTabId === id; + const urlSearch = getSearch(tab, { + filters, + query, + savedQuery, + sourcerer, + timeline, + timerange, + }); + + const handleClick = (ev: React.MouseEvent) => { + ev.preventDefault(); + navigateToApp(`${APP_ID}:${id}`, { path: urlSearch }); + track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`); + }; + + const appHref = getUrlForApp(`${APP_ID}:${id}`, { path: urlSearch }); + + return { + 'data-href': appHref, + 'data-test-subj': `navigation-${id}`, + disabled, + href: appHref, + id, + isSelected, + name, + onClick: handleClick, + }; + }); + + return [ + { + id: APP_ID, // TODO: When separating into sub-sections (detect, explore, investigate). Those names can also serve as the section id + items: navItems, + name: '', + }, + ]; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx new file mode 100644 index 0000000000000..390f44b48b0b1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getOr } from 'lodash/fp'; +import { useEffect, useState, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { PrimaryNavigationProps } from './types'; +import { usePrimaryNavigationItems } from './use_navigation_items'; +import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public'; + +const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mainLabel', { + defaultMessage: 'Security', +}); + +export const usePrimaryNavigation = ({ + filters, + query, + navTabs, + pageName, + savedQuery, + sourcerer, + timeline, + timerange, +}: PrimaryNavigationProps): KibanaPageTemplateProps['solutionNav'] => { + const mapLocationToTab = useCallback( + (): string => + getOr( + '', + 'id', + Object.values(navTabs).find((item) => pageName === item.id && item.pageId == null) + ), + [pageName, navTabs] + ); + + const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab()); + + useEffect(() => { + const currentTabSelected = mapLocationToTab(); + + if (currentTabSelected !== selectedTabId) { + setSelectedTabId(currentTabSelected); + } + + // we do need navTabs in case the selectedTabId appears after initial load (ex. checking permissions for anomalies) + }, [pageName, navTabs, mapLocationToTab, selectedTabId]); + + const navItems = usePrimaryNavigationItems({ + filters, + navTabs, + query, + savedQuery, + selectedTabId, + sourcerer, + timeline, + timerange, + }); + + return { + name: translatedNavTitle, + icon: 'logoSecurity', + items: navItems, + }; +}; 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 30b89086fb99c..051c1bd8ae5cb 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 @@ -5,14 +5,10 @@ * 2.0. */ -import { EuiBadge, EuiDescriptionList, EuiFlexGroup, EuiIcon, EuiPage } from '@elastic/eui'; +import { EuiBadge, EuiDescriptionList, EuiFlexGroup, EuiIcon } from '@elastic/eui'; import styled, { createGlobalStyle } from 'styled-components'; -import { - GLOBAL_HEADER_HEIGHT, - FULL_SCREEN_TOGGLED_CLASS_NAME, - SCROLLING_DISABLED_CLASS_NAME, -} from '../../../../common/constants'; +import { FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../common/constants'; export const SecuritySolutionAppWrapper = styled.div` display: flex; @@ -27,25 +23,6 @@ SecuritySolutionAppWrapper.displayName = 'SecuritySolutionAppWrapper'; and `EuiPopover`, `EuiToolTip` global styles */ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimary: string } } }>` - // fixes double scrollbar on views with EventsTable - #kibana-body { - overflow: hidden; - } - - div.kbnAppWrapper { - background-color: rgba(0,0,0,0); - } - - div.application { - background-color: rgba(0,0,0,0); - - // Security App wrapper - > div { - display: flex; - flex: 1 1 auto; - } - } - .euiPopover__panel.euiPopover__panel-isOpen { z-index: 9900 !important; min-width: 24px; @@ -82,10 +59,6 @@ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimar ${({ theme }) => `background-color: ${theme.eui.euiColorPrimary} !important`}; } - .${SCROLLING_DISABLED_CLASS_NAME} ${SecuritySolutionAppWrapper} { - max-height: calc(100vh - ${GLOBAL_HEADER_HEIGHT}px); - } - /* EuiScreenReaderOnly has a default 1px height and width. These extra pixels were adding additional height to every table row in the alerts table on the @@ -122,96 +95,6 @@ export const DescriptionListStyled = styled(EuiDescriptionList)` DescriptionListStyled.displayName = 'DescriptionListStyled'; -export const PageContainer = styled.div` - display: flex; - flex-direction: column; - align-items: stretch; - background-color: ${(props) => props.theme.eui.euiColorEmptyShade}; - height: 100%; - padding: 1rem; - overflow: hidden; - margin: 0px; -`; - -PageContainer.displayName = 'PageContainer'; - -export const PageContent = styled.div` - flex: 1 1 auto; - height: 100%; - position: relative; - overflow-y: hidden; - background-color: ${(props) => props.theme.eui.euiColorEmptyShade}; - margin-top: 62px; -`; - -PageContent.displayName = 'PageContent'; - -export const FlexPage = styled(EuiPage)` - flex: 1 0 0; -`; - -FlexPage.displayName = 'FlexPage'; - -export const PageHeader = styled.div` - background-color: ${(props) => props.theme.eui.euiColorEmptyShade}; - display: flex; - user-select: none; - padding: 1rem 1rem 0rem 1rem; - width: 100vw; - position: fixed; -`; - -PageHeader.displayName = 'PageHeader'; - -export const FooterContainer = styled.div` - flex: 0; - bottom: 0; - color: #666; - left: 0; - position: fixed; - text-align: left; - user-select: none; - width: 100%; - background-color: #f5f7fa; - padding: 16px; - border-top: 1px solid #d3dae6; -`; - -FooterContainer.displayName = 'FooterContainer'; - -export const PaneScrollContainer = styled.div` - height: 100%; - overflow-y: scroll; - > div:last-child { - margin-bottom: 3rem; - } -`; - -PaneScrollContainer.displayName = 'PaneScrollContainer'; - -export const Pane = styled.div` - height: 100%; - overflow: hidden; - user-select: none; -`; - -Pane.displayName = 'Pane'; - -export const PaneHeader = styled.div` - display: flex; -`; - -PaneHeader.displayName = 'PaneHeader'; - -export const Pane1FlexContent = styled.div` - display: flex; - flex-direction: row; - flex-wrap: wrap; - height: 100%; -`; - -Pane1FlexContent.displayName = 'Pane1FlexContent'; - export const CountBadge = (styled(EuiBadge)` margin-left: 5px; ` as unknown) as typeof EuiBadge; diff --git a/x-pack/plugins/security_solution/public/common/components/page_wrapper/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/page_wrapper/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..5da587f23693b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/page_wrapper/__snapshots__/index.test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SecuritySolutionPageWrapper it renders 1`] = ` + +

+ Test page +

+
+`; diff --git a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.test.tsx similarity index 65% rename from x-pack/plugins/security_solution/public/common/components/wrapper_page/index.test.tsx rename to x-pack/plugins/security_solution/public/common/components/page_wrapper/index.test.tsx index 3ec1e44205dd3..f6ebf2a90abb4 100644 --- a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.test.tsx @@ -9,18 +9,18 @@ import { shallow } from 'enzyme'; import React from 'react'; import { TestProviders } from '../../mock'; -import { WrapperPage } from './index'; +import { SecuritySolutionPageWrapper } from './index'; -describe('WrapperPage', () => { +describe('SecuritySolutionPageWrapper', () => { test('it renders', () => { const wrapper = shallow( - +

{'Test page'}

-
+
); - expect(wrapper.find('Memo(WrapperPageComponent)')).toMatchSnapshot(); + expect(wrapper.find('Memo(SecuritySolutionPageWrapperComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx similarity index 68% rename from x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx rename to x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx index a3eb76a2728bf..82e0ded264b06 100644 --- a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx @@ -15,30 +15,26 @@ import { gutterTimeline } from '../../lib/helpers'; import { AppGlobalStyle } from '../page/index'; const Wrapper = styled.div` - padding: ${(props) => `${props.theme.eui.paddingSizes.l}`}; - - &.siemWrapperPage--fullHeight { + &.securitySolutionWrapper--fullHeight { height: 100%; display: flex; flex-direction: column; flex: 1 1 auto; } - - &.siemWrapperPage--noPadding { + &.securitySolutionWrapper--noPadding { padding: 0; display: flex; flex-direction: column; flex: 1 1 auto; } - - &.siemWrapperPage--withTimeline { + &.securitySolutionWrapper--withTimeline { padding-bottom: ${gutterTimeline}; } `; Wrapper.displayName = 'Wrapper'; -interface WrapperPageProps { +interface SecuritySolutionPageWrapperProps { children: React.ReactNode; restrictWidth?: boolean | number | string; style?: Record; @@ -46,24 +42,19 @@ interface WrapperPageProps { noTimeline?: boolean; } -const WrapperPageComponent: React.FC = ({ - children, - className, - style, - noPadding, - noTimeline, - ...otherProps -}) => { +const SecuritySolutionPageWrapperComponent: React.FC< + SecuritySolutionPageWrapperProps & CommonProps +> = ({ children, className, style, noPadding, noTimeline, ...otherProps }) => { const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); useEffect(() => { setGlobalFullScreen(false); // exit full screen mode on page load }, [setGlobalFullScreen]); const classes = classNames(className, { - siemWrapperPage: true, - 'siemWrapperPage--noPadding': noPadding, - 'siemWrapperPage--withTimeline': !noTimeline, - 'siemWrapperPage--fullHeight': globalFullScreen, + securitySolutionWrapper: true, + 'securitySolutionWrapper--noPadding': noPadding, + 'securitySolutionWrapper--withTimeline': !noTimeline, + 'securitySolutionWrapper--fullHeight': globalFullScreen, }); return ( @@ -74,4 +65,4 @@ const WrapperPageComponent: React.FC = ({ ); }; -export const WrapperPage = React.memo(WrapperPageComponent); +export const SecuritySolutionPageWrapper = React.memo(SecuritySolutionPageWrapperComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/panel/index.tsx b/x-pack/plugins/security_solution/public/common/components/panel/index.tsx index 652d22409cb0c..802fd4c7f44a6 100644 --- a/x-pack/plugins/security_solution/public/common/components/panel/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/panel/index.tsx @@ -25,7 +25,7 @@ import { EuiPanel } from '@elastic/eui'; * Ref: https://www.styled-components.com/docs/faqs#why-am-i-getting-html-attribute-warnings * Ref: https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html */ -export const Panel = styled(({ loading, ...props }) => )` +export const Panel = styled(({ loading, ...props }) => )` position: relative; ${({ loading }) => loading && diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx index 5b4a8f67aa361..2d8d55a5c943f 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx @@ -222,7 +222,7 @@ export const StatItemsComponent = React.memo( return ( - + diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx index a2d5076031328..8a7c6bcb4a9b5 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx @@ -29,7 +29,6 @@ import { SecurityPageName } from '../../../../common/constants'; export const dispatchSetInitialStateFromUrl = ( dispatch: Dispatch ): DispatchSetInitialStateFromUrl => ({ - detailName, filterManager, indexPattern, pageName, diff --git a/x-pack/plugins/security_solution/public/common/components/wrapper_page/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/wrapper_page/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 89ed2f45a6bf1..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/wrapper_page/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`WrapperPage it renders 1`] = ` - -

- Test page -

-
-`; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_global_header_portal.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_global_header_portal.tsx index 5b5877a4c2ded..8e8d73ff12849 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_global_header_portal.tsx +++ b/x-pack/plugins/security_solution/public/common/hooks/use_global_header_portal.tsx @@ -11,10 +11,10 @@ import { createPortalNode } from 'react-reverse-portal'; /** * A singleton portal for rendering content in the global header */ -const globalHeaderPortalNodeSingleton = createPortalNode(); +const globalKQLHeaderPortalNodeSingleton = createPortalNode(); export const useGlobalHeaderPortal = () => { - const [globalHeaderPortalNode] = useState(globalHeaderPortalNodeSingleton); + const [globalKQLHeaderPortalNode] = useState(globalKQLHeaderPortalNodeSingleton); - return { globalHeaderPortalNode }; + return { globalKQLHeaderPortalNode }; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx index 91b5a10684405..d766104e356eb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx @@ -298,7 +298,7 @@ export const AlertsHistogramPanel = memo( return ( - + = ({ if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) { return ( - + diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx index fd0be8e002193..3b41c9280998b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx @@ -6,6 +6,7 @@ */ import React, { memo } from 'react'; +import { EuiSpacer } from '@elastic/eui'; import { CallOutMessage, CallOutPersistentSwitcher } from '../../../../common/components/callouts'; import { useUserData } from '../../user_info'; @@ -33,20 +34,22 @@ const needAdminForUpdateRulesMessage: CallOutMessage = { * hasIndexManage is also true, then the user should be performing the update on the page which is * why we do not show it for that condition. */ -const NeedAdminForUpdateCallOutComponent = (): JSX.Element => { +const NeedAdminForUpdateCallOutComponent = (): JSX.Element | null => { const [{ signalIndexMappingOutdated, hasIndexManage }] = useUserData(); const signalIndexMappingIsOutdated = signalIndexMappingOutdated != null && signalIndexMappingOutdated; const userDoesntHaveIndexManage = hasIndexManage != null && !hasIndexManage; - - return ( - - ); + const shouldShowCallout = signalIndexMappingIsOutdated && userDoesntHaveIndexManage; + + // Passing shouldShowCallout to the condition param will end up with an unecessary spacer being rendered + return shouldShowCallout ? ( + <> + + + + ) : null; }; export const NeedAdminForUpdateRulesCallOut = memo(NeedAdminForUpdateCallOutComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/no_api_integration_callout/index.tsx b/x-pack/plugins/security_solution/public/detections/components/callouts/no_api_integration_callout/index.tsx index f21c66380f30a..7b483930db505 100644 --- a/x-pack/plugins/security_solution/public/detections/components/callouts/no_api_integration_callout/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/callouts/no_api_integration_callout/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiCallOut, EuiButton } from '@elastic/eui'; +import { EuiCallOut, EuiButton, EuiSpacer } from '@elastic/eui'; import React, { memo, useCallback, useState } from 'react'; import * as i18n from './translations'; @@ -15,12 +15,15 @@ const NoApiIntegrationKeyCallOutComponent = () => { const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]); return showCallOut ? ( - -

{i18n.NO_API_INTEGRATION_KEY_CALLOUT_MSG}

- - {i18n.DISMISS_CALLOUT} - -
+ <> + +

{i18n.NO_API_INTEGRATION_KEY_CALLOUT_MSG}

+ + {i18n.DISMISS_CALLOUT} + +
+ + ) : null; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx index a09afa3ca2164..c1078e1ba77e7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx @@ -82,7 +82,7 @@ const StepAboutRuleToggleDetailsComponent: React.FC = ({ ); return ( - + {loading && ( <> diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_panel/index.tsx index f9e6031d826ca..ac9a153ad76bf 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_panel/index.tsx @@ -24,7 +24,7 @@ const MyPanel = styled(EuiPanel)` MyPanel.displayName = 'MyPanel'; const StepPanelComponent: React.FC = ({ children, loading, title }) => ( - + {loading && } {children} diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx index dbad1c57fda77..3d81735122e73 100644 --- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx @@ -216,7 +216,7 @@ export const ValueListsModalComponent: React.FC = ({ - +

{i18n.TABLE_TITLE}

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 1c31dfd3b8907..0c12d8256d66d 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 @@ -22,7 +22,7 @@ import { UpdateDateRange } from '../../../common/components/charts/common'; import { FiltersGlobal } from '../../../common/components/filters_global'; import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../../common/components/search_bar'; -import { WrapperPage } from '../../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; import { inputsSelectors } from '../../../common/store/inputs'; import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; @@ -197,22 +197,22 @@ const DetectionEnginePageComponent = () => { if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { return ( - + - + ); } if (!loading && (isSignalIndexExists === false || needsListsConfiguration)) { return ( - + - + ); } @@ -228,7 +228,7 @@ const DetectionEnginePageComponent = () => { - + { onShowOnlyThreatIndicatorAlertsChanged={onShowOnlyThreatIndicatorAlertsCallback} to={to} /> - + ) : ( - + - + )} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx index 90247d19e0503..23edf785a7f3a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx @@ -26,7 +26,7 @@ import { getRuleDetailsUrl, getRulesUrl, } from '../../../../../common/components/link_to/redirect_to_detection_engine'; -import { WrapperPage } from '../../../../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../../../../common/components/page_wrapper'; import { displaySuccessToast, useStateToaster } from '../../../../../common/components/toasters'; import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; import { useUserData } from '../../../../components/user_info'; @@ -287,7 +287,7 @@ const CreateRulePageComponent: React.FC = () => { return ( <> - + { text: i18n.BACK_TO_RULES, pageId: SecurityPageName.detections, }} - border isLoading={isLoading || loading} title={i18n.PAGE_TITLE} /> - + { - + { - + { - + { - + diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/failure_history.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/failure_history.tsx index 417e1c989ce9b..2fedd6160af2c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/failure_history.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/failure_history.tsx @@ -29,7 +29,7 @@ const FailureHistoryComponent: React.FC = ({ id }) => { const [loading, ruleStatus] = useRuleStatus(id); if (loading) { return ( - + @@ -60,7 +60,7 @@ const FailureHistoryComponent: React.FC = ({ id }) => { }, ]; return ( - + { - + { /> )} {ruleDetailTab === RuleDetailTabs.failures && } - + ) : ( - + - + )} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx index 2d751459eb12f..41710a822e539 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx @@ -21,7 +21,7 @@ import { useParams, useHistory } from 'react-router-dom'; import { UpdateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; import { useRule, useUpdateRule } from '../../../../containers/detection_engine/rules'; import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; -import { WrapperPage } from '../../../../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../../../../common/components/page_wrapper'; import { getRuleDetailsUrl, getDetectionEngineUrl, @@ -335,7 +335,7 @@ const EditRulePageComponent: FC = () => { return ( <> - + {
- + diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx index 8bacb10444a7d..29fd8e2e8b247 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx @@ -16,7 +16,7 @@ import { getCreateRuleUrl, } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { DetectionEngineHeaderPage } from '../../../components/detection_engine_header_page'; -import { WrapperPage } from '../../../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { useUserData } from '../../../components/user_info'; @@ -182,7 +182,7 @@ const RulesPageComponent: React.FC = () => { subtitle={i18n.INITIAL_PROMPT_TEXT} title={i18n.IMPORT_RULE} /> - + { rulesNotUpdated={rulesNotUpdated} setRefreshRulesData={handleSetRefreshRulesData} /> - + diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index d88e4f048f917..22edd2c19d6bd 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -21,11 +21,11 @@ import { hostToCriteria } from '../../../common/components/ml/criteria/host_to_c import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml_user_permissions'; import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; import { scoreIntervalToDateTime } from '../../../common/components/ml/score/score_interval_to_datetime'; -import { SiemNavigation } from '../../../common/components/navigation'; +import { SecuritySolutionTabNavigation } from '../../../common/components/navigation'; import { HostsDetailsKpiComponent } from '../../components/kpi_hosts'; import { HostOverview } from '../../../overview/components/host_overview'; import { SiemSearchBar } from '../../../common/components/search_bar'; -import { WrapperPage } from '../../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; import { useGlobalTime } from '../../../common/containers/use_global_time'; import { useKibana } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; @@ -123,7 +123,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta - + = ({ detailName, hostDeta - @@ -207,14 +207,14 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta indexPattern={indexPattern} setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker} /> - + ) : ( - + - + )} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx index f1eab38c56db0..d05b091381cca 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx @@ -18,7 +18,7 @@ import { kibanaObservable, createSecuritySolutionStorageMock, } from '../../common/mock'; -import { SiemNavigation } from '../../common/components/navigation'; +import { SecuritySolutionTabNavigation } from '../../common/components/navigation'; import { inputsActions } from '../../common/store/inputs'; import { State, createStore } from '../../common/store'; import { Hosts } from './hosts'; @@ -102,7 +102,7 @@ describe('Hosts - rendering', () => { ); - expect(wrapper.find(SiemNavigation).exists()).toBe(true); + expect(wrapper.find(SecuritySolutionTabNavigation).exists()).toBe(true); }); test('it should add the new filters after init', async () => { 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 ce0385b532fd5..7d31d291e75f1 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -19,10 +19,10 @@ import { FiltersGlobal } from '../../common/components/filters_global'; import { HeaderPage } from '../../common/components/header_page'; import { LastEventTime } from '../../common/components/last_event_time'; import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions'; -import { SiemNavigation } from '../../common/components/navigation'; +import { SecuritySolutionTabNavigation } from '../../common/components/navigation'; import { HostsKpiComponent } from '../components/kpi_hosts'; import { SiemSearchBar } from '../../common/components/search_bar'; -import { WrapperPage } from '../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useGlobalFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { TimelineId } from '../../../common/types/timeline'; @@ -164,10 +164,9 @@ const HostsComponent = () => { - + { - + @@ -207,14 +208,14 @@ const HostsComponent = () => { from={from} type={hostsModel.HostsType.page} /> - + ) : ( - + - + )} diff --git a/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts b/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts index 76acff7847671..3bcbd81621588 100644 --- a/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts +++ b/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts @@ -11,7 +11,7 @@ import { AdministrationSubTab } from '../types'; import { ENDPOINTS_TAB, EVENT_FILTERS_TAB, POLICIES_TAB, TRUSTED_APPS_TAB } from './translations'; import { AdministrationRouteSpyState } from '../../common/utils/route/types'; import { GetUrlForApp } from '../../common/components/navigation/types'; -import { ADMINISTRATION } from '../../app/home/translations'; +import { ADMINISTRATION } from '../../app/translations'; import { APP_ID, SecurityPageName } from '../../../common/constants'; const TabNameMappedToI18nKey: Record = { diff --git a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx index 72a6de2a2de8d..021c900824f8d 100644 --- a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx @@ -9,9 +9,9 @@ import React, { FC, memo } from 'react'; import { EuiPanel, EuiSpacer, CommonProps } from '@elastic/eui'; import styled from 'styled-components'; import { SecurityPageName } from '../../../common/constants'; -import { WrapperPage } from '../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { HeaderPage } from '../../common/components/header_page'; -import { SiemNavigation } from '../../common/components/navigation'; +import { SecuritySolutionTabNavigation } from '../../common/components/navigation'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { AdministrationSubTab } from '../types'; import { @@ -46,7 +46,7 @@ export const AdministrationListPage: FC + - - {children} + {children} - + ); } ); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index 204c3a86ce3e6..e9cdd16554f33 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -42,7 +42,7 @@ import { useFormatUrl } from '../../../../common/components/link_to'; import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { MANAGEMENT_APP_ID } from '../../../common/constants'; import { PolicyDetailsRouteState } from '../../../../../common/endpoint/types'; -import { WrapperPage } from '../../../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; import { HeaderPage } from '../../../../common/components/header_page'; import { PolicyDetailsForm } from './policy_details_form'; @@ -51,7 +51,7 @@ const PolicyDetailsHeader = styled.div` padding: ${(props) => props.theme.eui.paddingSizes.xl} 0; background-color: #fafbfd; border-bottom: 1px solid #d3dae6; - .siemHeaderPage { + .securitySolutionHeaderPage { max-width: ${maxFormWidth}; margin: 0 auto; } @@ -159,7 +159,7 @@ export const PolicyDetails = React.memo(() => { // Else, if we have an error, then show error on the page. if (!policyItem) { return ( - + {isPolicyLoading ? ( ) : policyApiError ? ( @@ -168,7 +168,7 @@ export const PolicyDetails = React.memo(() => { ) : null} - + ); } @@ -190,7 +190,7 @@ export const PolicyDetails = React.memo(() => { onConfirm={handleSaveConfirmation} /> )} - { - + diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap index e984ea5bb1711..51b60c8ff292b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -427,7 +427,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="body-content undefined" >

diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embeddable.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embeddable.tsx index 82b5b8a3e7b3d..3087dbe4ad6ed 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/embeddable.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embeddable.tsx @@ -20,7 +20,9 @@ export interface EmbeddableProps { export const Embeddable = React.memo(({ children }) => (

- {children} + + {children} +
)); Embeddable.displayName = 'Embeddable'; diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx index 4cccb536c08bb..02be5f78261c1 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx @@ -28,7 +28,7 @@ import { manageQuery } from '../../../common/components/page/manage_query'; import { FlowTargetSelectConnected } from '../../components/flow_target_select_connected'; import { IpOverview } from '../../components/details'; import { SiemSearchBar } from '../../../common/components/search_bar'; -import { WrapperPage } from '../../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; import { useNetworkDetails } from '../../containers/details'; import { useKibana } from '../../../common/lib/kibana'; import { decodeIpv6 } from '../../../common/lib/helpers'; @@ -128,7 +128,7 @@ const NetworkDetailsComponent: React.FC = () => { - + { hideHistogramIfEmpty={true} AnomaliesTableComponent={AnomaliesNetworkTable} /> - + ) : ( - + - + )} diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index dbfb250095ee2..13c04a5e5ec5b 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -20,11 +20,11 @@ import { EmbeddedMap } from '../components/embeddables/embedded_map'; import { FiltersGlobal } from '../../common/components/filters_global'; import { HeaderPage } from '../../common/components/header_page'; import { LastEventTime } from '../../common/components/last_event_time'; -import { SiemNavigation } from '../../common/components/navigation'; +import { SecuritySolutionTabNavigation } from '../../common/components/navigation'; import { NetworkKpiComponent } from '../components/kpi_network'; import { SiemSearchBar } from '../../common/components/search_bar'; -import { WrapperPage } from '../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useGlobalFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { LastEventIndexKey } from '../../../common/search_strategy'; @@ -155,10 +155,9 @@ const NetworkComponent = React.memo( - + ( - + @@ -217,13 +216,13 @@ const NetworkComponent = React.memo( ) : ( )} - + ) : ( - + - + )} diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx index 70f44a0008cbc..f11b849f5df6b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx @@ -115,7 +115,7 @@ const OverviewHostComponent: React.FC = ({ return ( - + <>{hostPageButton} diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx index 107a47f6cc132..39fb6ff08ee53 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx @@ -120,7 +120,7 @@ const OverviewNetworkComponent: React.FC = ({ return ( - + <> {networkPageButton} diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 4270d8ec164b3..2cf998e5e133a 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -12,7 +12,7 @@ import styled from 'styled-components'; import { AlertsByCategory } from '../components/alerts_by_category'; import { FiltersGlobal } from '../../common/components/filters_global'; import { SiemSearchBar } from '../../common/components/search_bar'; -import { WrapperPage } from '../../common/components/wrapper_page'; +import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { useFetchIndex } from '../../common/containers/source'; @@ -37,6 +37,10 @@ const SidebarFlexItem = styled(EuiFlexItem)` margin-right: 24px; `; +const StyledSecuritySolutionPageWrapper = styled(SecuritySolutionPageWrapper)` + overflow-x: auto; +`; + const OverviewComponent = () => { const getGlobalFiltersQuerySelector = useMemo( () => inputsSelectors.globalFiltersQuerySelector(), @@ -73,7 +77,7 @@ const OverviewComponent = () => { - + {!dismissMessage && !metadataIndexExists && isIngestEnabled && ( <> @@ -139,7 +143,7 @@ const OverviewComponent = () => { - + ) : ( diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 5a44faa58414a..32e6748f38141 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -61,7 +61,7 @@ import { DETECTION_ENGINE, CASE, ADMINISTRATION, -} from './app/home/translations'; +} from './app/translations'; import { IndexFieldsStrategyRequest, IndexFieldsStrategyResponse, diff --git a/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx b/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx index 45f7e6950b006..1f520a1847053 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx @@ -207,7 +207,7 @@ export const GraphControls = React.memo( /> - +
- +