Skip to content

Commit

Permalink
Implement new security solution wrapper (#100405)
Browse files Browse the repository at this point in the history
Co-authored-by: cchaos <caroline.horn@elastic.co>
  • Loading branch information
michaelolo24 and cchaos authored Jun 23, 2021
1 parent 77b5b23 commit 702661d
Show file tree
Hide file tree
Showing 98 changed files with 1,213 additions and 868 deletions.
1 change: 1 addition & 0 deletions src/core/public/rendering/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
@mixin kbnAffordForHeader($headerHeight) {
@include euiHeaderAffordForFixed($headerHeight);

#securitySolutionStickyKQL,
#app-fixed-viewport {
top: $headerHeight;
}
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/public/components/panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => <EuiPanel {...props} />)`
export const Panel = styled(({ loading, ...props }) => <EuiPanel {...props} hasBorder />)`
position: relative;
${({ loading }) =>
loading &&
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import {
TIMELINE_BOTTOM_BAR_CONTAINER,
TIMELINE_EVENT,
TIMELINE_EVENTS_COUNT_NEXT_PAGE,
TIMELINE_EVENTS_COUNT_PER_PAGE,
Expand Down Expand Up @@ -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);
});
});
2 changes: 2 additions & 0 deletions x-pack/plugins/security_solution/cypress/screens/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]';
Expand Down
6 changes: 3 additions & 3 deletions x-pack/plugins/security_solution/public/app/404.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => (
<WrapperPage>
<SecuritySolutionPageWrapper>
<FormattedMessage
id="xpack.securitySolution.pages.fourohfour.noContentFoundDescription"
defaultMessage="No content found"
/>
</WrapperPage>
</SecuritySolutionPageWrapper>
));

NotFoundPage.displayName = 'NotFoundPage';
26 changes: 22 additions & 4 deletions x-pack/plugins/security_solution/public/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -30,10 +30,17 @@ interface StartAppComponent {
children: React.ReactNode;
history: History;
onAppLeave: (handler: AppLeaveHandler) => void;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
store: Store<State, Action>;
}

const StartAppComponent: FC<StartAppComponent> = ({ children, history, onAppLeave, store }) => {
const StartAppComponent: FC<StartAppComponent> = ({
children,
history,
setHeaderActionMenu,
onAppLeave,
store,
}) => {
const { i18n } = useKibana().services;
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);

Expand All @@ -46,7 +53,11 @@ const StartAppComponent: FC<StartAppComponent> = ({ children, history, onAppLeav
<MlCapabilitiesProvider>
<UserPrivilegesProvider>
<ManageUserInfo>
<PageRouter history={history} onAppLeave={onAppLeave}>
<PageRouter
history={history}
onAppLeave={onAppLeave}
setHeaderActionMenu={setHeaderActionMenu}
>
{children}
</PageRouter>
</ManageUserInfo>
Expand All @@ -69,6 +80,7 @@ interface SecurityAppComponentProps {
history: History;
onAppLeave: (handler: AppLeaveHandler) => void;
services: StartServices;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
store: Store<State, Action>;
}

Expand All @@ -77,6 +89,7 @@ const SecurityAppComponent: React.FC<SecurityAppComponentProps> = ({
history,
onAppLeave,
services,
setHeaderActionMenu,
store,
}) => (
<KibanaContextProvider
Expand All @@ -85,7 +98,12 @@ const SecurityAppComponent: React.FC<SecurityAppComponentProps> = ({
...services,
}}
>
<StartApp history={history} onAppLeave={onAppLeave} store={store}>
<StartApp
history={history}
onAppLeave={onAppLeave}
setHeaderActionMenu={setHeaderActionMenu}
store={store}
>
{children}
</StartApp>
</KibanaContextProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -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(<OutPortal node={portalNode} />);
unmount = mount(element);
return unmount;
});

return () => {
portalNode.unmount();
unmount();
};
}, [portalNode, setHeaderActionMenu]);

return (
<InPortal node={portalNode}>
<EuiHeaderSection side="right">
{window.location.pathname.includes(APP_DETECTIONS_PATH) && (
<EuiHeaderSectionItem>
<MlPopover />
</EuiHeaderSectionItem>
)}
<EuiHeaderSectionItem>
<EuiHeaderLinks>
<EuiHeaderLink
color="primary"
data-test-subj="add-data"
href={http.basePath.prepend(ADD_DATA_PATH)}
iconType="indexOpen"
>
{BUTTON_ADD_DATA}
</EuiHeaderLink>
</EuiHeaderLinks>
</EuiHeaderSectionItem>
</EuiHeaderSection>
</InPortal>
);
}
);
GlobalHeader.displayName = 'GlobalHeader';
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
71 changes: 20 additions & 51 deletions x-pack/plugins/security_solution/public/app/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HomePageProps> = ({ children, onAppLeave }) => {
const { application, overlays } = useKibana().services;
const HomePageComponent: React.FC<HomePageProps> = ({
children,
onAppLeave,
setHeaderActionMenu,
}) => {
const { application } = useKibana().services;
const subPluginId = useRef<string>('');
const { ref, height = 0 } = useThrottledResizeObserver(300);
const banners$ = overlays.banners.get$();
const [headerFixed, setHeaderFixed] = useState<boolean>(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 ?? '';
Expand All @@ -66,13 +44,13 @@ const HomePageComponent: React.FC<HomePageProps> = ({ 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
Expand All @@ -81,23 +59,14 @@ const HomePageComponent: React.FC<HomePageProps> = ({ children, onAppLeave }) =>
useUpgradeEndpointPackage();

return (
<SecuritySolutionAppWrapper>
<HeaderGlobal ref={ref} isFixed={headerFixed} />

<Main paddingTop={mainPaddingTop} data-test-subj="pageContainer">
<DragDropContextWrapper browserFields={browserFields}>
<UseUrlState indexPattern={indexPattern} navTabs={navTabs} />
{indicesExist && showTimeline && (
<>
<AutoSaveWarningMsg />
<Flyout timelineId={TimelineId.active} onAppLeave={onAppLeave} />
</>
)}

<SecuritySolutionAppWrapper className="kbnAppWrapper">
<GlobalHeader setHeaderActionMenu={setHeaderActionMenu} />
<DragDropContextWrapper browserFields={browserFields}>
<UseUrlState indexPattern={indexPattern} navTabs={navTabs} />
<SecuritySolutionTemplateWrapper onAppLeave={onAppLeave}>
{children}
</DragDropContextWrapper>
</Main>

</SecuritySolutionTemplateWrapper>
</DragDropContextWrapper>
<HelpMenu />
</SecuritySolutionAppWrapper>
);
Expand Down
Loading

0 comments on commit 702661d

Please sign in to comment.