[Security Solution] Full screen timeline, Collapse event (#71786)

## Full screen Timeline & Timeline-based views

- Adds a _Full screen_ mode to Timeline, and all Timeline-based views, including:
  - Detections
  - Detections > Rule details
  - Hosts > Events
  - Hosts > External alerts
  - Network > External alerts
  - Timeline
- Enter full screen from any Resolver
- Adds a `Collapse event` action for quickly collapsing an expanded Timeline event
- Hides the `Add to case action` in timeline-based Resolver views, so those actions are only enabled in Timeline (a `TODO`  from https://github.com/elastic/kibana/pull/70111)

### Full screen detections
![full-screen-detections](https://user-images.githubusercontent.com/4459398/87493332-d348f280-c609-11ea-9399-126d2259daa2.gif)

### Enter full screen from any Resolver
![full-screen-resolver](https://user-images.githubusercontent.com/4459398/87493348-de038780-c609-11ea-86a3-52ab24055e38.gif)

### Full screen Timeline
![full-screen-timeline](https://user-images.githubusercontent.com/4459398/87493394-f4114800-c609-11ea-8d62-4add291d937a.gif)

### Collapse event
![collapse-event](https://user-images.githubusercontent.com/4459398/87493408-fa9fbf80-c609-11ea-88c8-fa87d82d1eb1.gif)

### Sort tooltip
![sort-tooltip](https://user-images.githubusercontent.com/4459398/87493417-012e3700-c60a-11ea-9905-44e3b7cfe60f.gif)
This commit is contained in:
Andrew Goldstein 2020-07-15 04:12:34 -06:00 committed by GitHub
parent e4f7acb90f
commit 0c0aaf0e6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
179 changed files with 1939 additions and 882 deletions

View file

@ -32,6 +32,8 @@ export const DEFAULT_INTERVAL_PAUSE = true;
export const DEFAULT_INTERVAL_TYPE = 'manual';
export const DEFAULT_INTERVAL_VALUE = 300000; // ms
export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges';
export const FILTERS_GLOBAL_HEIGHT = 109; // px
export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled';
export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51';
export const ENDPOINT_METADATA_INDEX = 'metrics-endpoint.metadata-*';

View file

@ -32,7 +32,7 @@ Main.displayName = 'Main';
const usersViewing = ['elastic']; // TODO: get the users viewing this timeline from Elasticsearch (persistance)
/** the global Kibana navigation at the top of every page */
const globalHeaderHeightPx = 48;
export const globalHeaderHeightPx = 48;
const calculateFlyoutHeight = ({
globalHeaderSize,

View file

@ -7,6 +7,7 @@
import React from 'react';
import { mount } from 'enzyme';
import '../../../common/mock/match_media';
import { ExternalServiceColumn } from './columns';
import { useGetCasesMockState } from '../../containers/mock';

View file

@ -7,6 +7,8 @@
import React from 'react';
import { mount } from 'enzyme';
import moment from 'moment-timezone';
import '../../../common/mock/match_media';
import { AllCases } from '.';
import { TestProviders } from '../../../common/mock';
import { useGetCasesMockState } from '../../containers/mock';

View file

@ -5,6 +5,7 @@
*/
import { mount } from 'enzyme';
import React from 'react';
import '../../../common/mock/match_media';
import { AllCasesModal } from '.';
import { TestProviders } from '../../../common/mock';

View file

@ -7,6 +7,7 @@
import React from 'react';
import { mount } from 'enzyme';
import '../../../common/mock/match_media';
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
import { CaseComponent, CaseProps, CaseView } from '.';
import { basicCase, basicCaseClosed, caseUserActions } from '../../containers/mock';

View file

@ -8,6 +8,7 @@ import React from 'react';
import { ReactWrapper, mount } from 'enzyme';
import { EuiText } from '@elastic/eui';
import '../../../common/mock/match_media';
import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button';
import { TestProviders } from '../../../common/mock';
import { searchURL } from './__mock__';

View file

@ -6,6 +6,8 @@
/* eslint-disable react/display-name */
import React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';
import '../../../common/mock/match_media';
import { usePushToService, ReturnUsePushToService, UsePushToService } from '.';
import { TestProviders } from '../../../common/mock';
import { usePostPushToService } from '../../containers/use_post_push_to_service';

View file

@ -58,6 +58,7 @@ const defaultAlertsFilters: Filter[] = [
interface Props {
timelineId: TimelineIdLiteral;
endDate: string;
eventsViewerBodyHeight?: number;
startDate: string;
pageFilters?: Filter[];
}
@ -65,6 +66,7 @@ interface Props {
const AlertsTableComponent: React.FC<Props> = ({
timelineId,
endDate,
eventsViewerBodyHeight,
startDate,
pageFilters = [],
}) => {
@ -91,6 +93,7 @@ const AlertsTableComponent: React.FC<Props> = ({
pageFilters={alertsFilter}
defaultModel={alertsDefaultModel}
end={endDate}
height={eventsViewerBodyHeight}
id={timelineId}
start={startDate}
/>

View file

@ -5,8 +5,18 @@
*/
import React, { useEffect, useCallback, useMemo } from 'react';
import numeral from '@elastic/numeral';
import { useWindowSize } from 'react-use';
import { globalHeaderHeightPx } from '../../../app/home';
import { DEFAULT_NUMBER_FORMAT, FILTERS_GLOBAL_HEIGHT } from '../../../../common/constants';
import { useFullScreen } from '../../containers/use_full_screen';
import { EVENTS_VIEWER_HEADER_HEIGHT } from '../events_viewer/events_viewer';
import {
getEventsViewerBodyHeight,
MIN_EVENTS_VIEWER_BODY_HEIGHT,
} from '../../../timelines/components/timeline/body/helpers';
import { footerHeight } from '../../../timelines/components/timeline/footer';
import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
import { AlertsComponentsProps } from './types';
import { AlertsTable } from './alerts_table';
import * as i18n from './translations';
@ -35,6 +45,8 @@ export const AlertsView = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const { height: windowHeight } = useWindowSize();
const { globalFullScreen } = useFullScreen();
const alertsHistogramConfigs: MatrixHisrogramConfigs = useMemo(
() => ({
...histogramConfigs,
@ -52,19 +64,32 @@ export const AlertsView = ({
return (
<>
<MatrixHistogramContainer
endDate={endDate}
filterQuery={filterQuery}
id={ID}
setQuery={setQuery}
sourceId="default"
startDate={startDate}
type={type}
{...alertsHistogramConfigs}
/>
{!globalFullScreen && (
<MatrixHistogramContainer
endDate={endDate}
filterQuery={filterQuery}
id={ID}
setQuery={setQuery}
sourceId="default"
startDate={startDate}
type={type}
{...alertsHistogramConfigs}
/>
)}
<AlertsTable
timelineId={timelineId}
endDate={endDate}
eventsViewerBodyHeight={
globalFullScreen
? getEventsViewerBodyHeight({
footerHeight,
headerHeight: EVENTS_VIEWER_HEADER_HEIGHT,
kibanaChromeHeight: globalHeaderHeightPx,
otherContentHeight: FILTERS_GLOBAL_HEIGHT,
windowHeight,
})
: MIN_EVENTS_VIEWER_BODY_HEIGHT
}
startDate={startDate}
pageFilters={pageFilters}
/>

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import '../../../common/mock/match_media';
import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts';
import {

View file

@ -12,6 +12,7 @@ import { ThemeProvider } from 'styled-components';
import { escapeDataProviderId } from '../drag_and_drop/helpers';
import { TestProviders } from '../../mock';
import '../../mock/match_media';
import { BarChartBaseComponent, BarChartComponent } from './barchart';
import { ChartSeriesData } from './common';

View file

@ -9,6 +9,7 @@ import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import { ThemeProvider } from 'styled-components';
import '../../mock/match_media';
import { TestProviders } from '../../mock';
import { MIN_LEGEND_HEIGHT, DraggableLegend } from './draggable_legend';

View file

@ -9,6 +9,7 @@ import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import { ThemeProvider } from 'styled-components';
import '../../mock/match_media';
import { TestProviders } from '../../mock';
import { DraggableLegendItem, LegendItem } from './draggable_legend_item';

View file

@ -9,6 +9,7 @@ import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { DraggableStateSnapshot, DraggingStyle } from 'react-beautiful-dnd';
import '../../mock/match_media';
import { mockBrowserFields, mocksSource } from '../../containers/source/mock';
import { TestProviders } from '../../mock';
import { mockDataProviders } from '../../../timelines/components/timeline/data_providers/mock/mock_data_providers';

View file

@ -9,6 +9,7 @@ import React from 'react';
import { useWithSource } from '../../containers/source';
import { mockBrowserFields } from '../../containers/source/mock';
import '../../mock/match_media';
import { useKibana } from '../../lib/kibana';
import { TestProviders } from '../../mock';
import { createKibanaCoreStartMock } from '../../mock/kibana_core';

View file

@ -8,6 +8,7 @@ import { shallow } from 'enzyme';
import React from 'react';
import { TestProviders } from '../../mock';
import '../../mock/match_media';
import { getEmptyString } from '../empty_value';
import { useMountAppended } from '../../utils/use_mount_appended';

View file

@ -4,6 +4,33 @@ exports[`EventDetails rendering should match snapshot 1`] = `
<Details
data-test-subj="eventDetails"
>
<styled.div>
<EuiPopover
anchorPosition="downCenter"
button={
<EuiToolTip
content="Collapse event"
delay="regular"
position="top"
>
<CollapseButton
aria-label="Collapse"
data-test-subj="collapse"
iconType="cross"
onClick={[MockFunction]}
size="s"
/>
</EuiToolTip>
}
closePopover={[Function]}
display="inlineBlock"
hasArrow={true}
isOpen={false}
ownFocus={false}
panelPaddingSize="m"
repositionOnScroll={true}
/>
</styled.div>
<EuiTabbedContent
autoFocus="initial"
onTabClick={[Function]}

View file

@ -7,6 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../mock/match_media';
import { mockDetailItemData, mockDetailItemDataId } from '../../mock/mock_detail_item';
import { TestProviders } from '../../mock/test_providers';
@ -29,6 +30,7 @@ describe('EventDetails', () => {
data={mockDetailItemData}
id={mockDetailItemDataId}
view="table-view"
onEventToggled={jest.fn()}
onUpdateColumns={jest.fn()}
onViewSelected={jest.fn()}
timelineId="test"
@ -50,6 +52,7 @@ describe('EventDetails', () => {
data={mockDetailItemData}
id={mockDetailItemDataId}
view="table-view"
onEventToggled={jest.fn()}
onUpdateColumns={jest.fn()}
onViewSelected={jest.fn()}
timelineId="test"
@ -76,6 +79,7 @@ describe('EventDetails', () => {
data={mockDetailItemData}
id={mockDetailItemDataId}
view="table-view"
onEventToggled={jest.fn()}
onUpdateColumns={jest.fn()}
onViewSelected={jest.fn()}
timelineId="test"
@ -88,5 +92,31 @@ describe('EventDetails', () => {
wrapper.find('[data-test-subj="eventDetails"]').find('.euiTab-isSelected').first().text()
).toEqual('Table');
});
test('it invokes `onEventToggled` when the collapse button is clicked', () => {
const onEventToggled = jest.fn();
const wrapper = mount(
<TestProviders>
<EventDetails
browserFields={mockBrowserFields}
columnHeaders={defaultHeaders}
data={mockDetailItemData}
id={mockDetailItemDataId}
view="table-view"
onEventToggled={onEventToggled}
onUpdateColumns={jest.fn()}
onViewSelected={jest.fn()}
timelineId="test"
toggleColumn={jest.fn()}
/>
</TestProviders>
);
wrapper.find('[data-test-subj="collapse"]').first().simulate('click');
wrapper.update();
expect(onEventToggled).toHaveBeenCalled();
});
});
});

View file

@ -4,8 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui';
import React from 'react';
import { noop } from 'lodash/fp';
import {
EuiButtonIcon,
EuiPopover,
EuiTabbedContent,
EuiTabbedContentTab,
EuiToolTip,
} from '@elastic/eui';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { BrowserFields } from '../../containers/source';
@ -15,15 +22,34 @@ import { OnUpdateColumns } from '../../../timelines/components/timeline/events';
import { EventFieldsBrowser } from './event_fields_browser';
import { JsonView } from './json_view';
import * as i18n from './translations';
import { COLLAPSE, COLLAPSE_EVENT } from '../../../timelines/components/timeline/body/translations';
export type View = 'table-view' | 'json-view';
const PopoverContainer = styled.div`
left: -40px;
position: relative;
top: 10px;
.euiPopover {
position: fixed;
z-index: 10;
}
`;
const CollapseButton = styled(EuiButtonIcon)`
border: 1px solid;
`;
CollapseButton.displayName = 'CollapseButton';
interface Props {
browserFields: BrowserFields;
columnHeaders: ColumnHeaderOptions[];
data: DetailItem[];
id: string;
view: View;
onEventToggled: () => void;
onUpdateColumns: OnUpdateColumns;
onViewSelected: (selected: View) => void;
timelineId: string;
@ -43,11 +69,27 @@ export const EventDetails = React.memo<Props>(
data,
id,
view,
onEventToggled,
onUpdateColumns,
onViewSelected,
timelineId,
toggleColumn,
}) => {
const button = useMemo(
() => (
<EuiToolTip content={COLLAPSE_EVENT}>
<CollapseButton
aria-label={COLLAPSE}
data-test-subj="collapse"
iconType="cross"
size="s"
onClick={onEventToggled}
/>
</EuiToolTip>
),
[onEventToggled]
);
const tabs: EuiTabbedContentTab[] = [
{
id: 'table-view',
@ -73,6 +115,14 @@ export const EventDetails = React.memo<Props>(
return (
<Details data-test-subj="eventDetails">
<PopoverContainer>
<EuiPopover
button={button}
isOpen={false}
closePopover={noop}
repositionOnScroll={true}
/>
</PopoverContainer>
<EuiTabbedContent
tabs={tabs}
selectedTab={view === 'table-view' ? tabs[0] : tabs[1]}

View file

@ -6,6 +6,7 @@
import React from 'react';
import '../../mock/match_media';
import { mockDetailItemData, mockDetailItemDataId } from '../../mock/mock_detail_item';
import { TestProviders } from '../../mock/test_providers';

View file

@ -18,13 +18,23 @@ interface Props {
columnHeaders: ColumnHeaderOptions[];
data: DetailItem[];
id: string;
onEventToggled: () => void;
onUpdateColumns: OnUpdateColumns;
timelineId: string;
toggleColumn: (column: ColumnHeaderOptions) => void;
}
export const StatefulEventDetails = React.memo<Props>(
({ browserFields, columnHeaders, data, id, onUpdateColumns, timelineId, toggleColumn }) => {
({
browserFields,
columnHeaders,
data,
id,
onEventToggled,
onUpdateColumns,
timelineId,
toggleColumn,
}) => {
const [view, setView] = useState<View>('table-view');
const handleSetView = useCallback((newView) => setView(newView), []);
@ -34,6 +44,7 @@ export const StatefulEventDetails = React.memo<Props>(
columnHeaders={columnHeaders}
data={data}
id={id}
onEventToggled={onEventToggled}
onUpdateColumns={onUpdateColumns}
onViewSelected={handleSetView}
timelineId={timelineId}

View file

@ -8,6 +8,7 @@ import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import useResizeObserver from 'use-resize-observer/polyfilled';
import '../../mock/match_media';
import { mockIndexPattern, TestProviders } from '../../mock';
import { wait } from '../../lib/helpers';

View file

@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiPanel } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import { getOr, isEmpty, union } from 'lodash/fp';
import React, { useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import deepEqual from 'fast-deep-equal';
import { BrowserFields, DocValueFields } from '../../containers/source';
@ -34,13 +34,40 @@ import {
} from '../../../../../../../src/plugins/data/public';
import { inputsModel } from '../../store';
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { ExitFullScreen } from '../exit_full_screen';
import { useFullScreen } from '../../containers/use_full_screen';
import { TimelineId } from '../../../../common/types/timeline';
export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px
const UTILITY_BAR_HEIGHT = 19; // px
const COMPACT_HEADER_HEIGHT = EVENTS_VIEWER_HEADER_HEIGHT - UTILITY_BAR_HEIGHT; // px
const UtilityBar = styled.div`
height: ${UTILITY_BAR_HEIGHT}px;
`;
const TitleText = styled.span`
margin-right: 12px;
`;
const DEFAULT_EVENTS_VIEWER_HEIGHT = 500;
const StyledEuiPanel = styled(EuiPanel)`
const StyledEuiPanel = styled(EuiPanel)<{ $isFullScreen: boolean }>`
${({ $isFullScreen }) =>
$isFullScreen &&
css`
border: 0;
box-shadow: none;
padding-top: 0;
padding-bottom: 0;
`}
max-width: 100%;
`;
const TitleFlexGroup = styled(EuiFlexGroup)`
margin-top: 8px;
`;
const EventsContainerLoading = styled.div`
width: 100%;
overflow: auto;
@ -98,6 +125,7 @@ const EventsViewerComponent: React.FC<Props> = ({
utilityBar,
graphEventId,
}) => {
const { globalFullScreen } = useFullScreen();
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const kibana = useKibana();
const [isQueryLoading, setIsQueryLoading] = useState(false);
@ -113,6 +141,20 @@ const EventsViewerComponent: React.FC<Props> = ({
id,
]);
const justTitle = useMemo(() => <TitleText data-test-subj="title">{title}</TitleText>, [title]);
const titleWithExitFullScreen = useMemo(
() => (
<TitleFlexGroup alignItems="center" data-test-subj="title-flex-group" gutterSize="none">
<EuiFlexItem grow={false}>{justTitle}</EuiFlexItem>
<EuiFlexItem grow={false}>
<ExitFullScreen />
</EuiFlexItem>
</TitleFlexGroup>
),
[justTitle]
);
const combinedQueries = combineQueries({
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
dataProviders,
@ -153,7 +195,10 @@ const EventsViewerComponent: React.FC<Props> = ({
);
return (
<StyledEuiPanel data-test-subj="events-viewer-panel">
<StyledEuiPanel
data-test-subj="events-viewer-panel"
$isFullScreen={globalFullScreen && id !== TimelineId.active}
>
{canQueryTimeline ? (
<EventDetailsWidthProvider>
<TimelineQuery
@ -188,10 +233,17 @@ const EventsViewerComponent: React.FC<Props> = ({
return (
<>
<HeaderSection id={id} subtitle={utilityBar ? undefined : subtitle} title={title}>
<HeaderSection
id={id}
height={headerFilterGroup ? COMPACT_HEADER_HEIGHT : EVENTS_VIEWER_HEADER_HEIGHT}
subtitle={utilityBar ? undefined : subtitle}
title={inspect ? justTitle : titleWithExitFullScreen}
>
{headerFilterGroup}
</HeaderSection>
{utilityBar?.(refetch, totalCountMinusDeleted)}
{utilityBar && (
<UtilityBar>{utilityBar?.(refetch, totalCountMinusDeleted)}</UtilityBar>
)}
<EventsContainerLoading data-test-subj={`events-container-loading-${loading}`}>
<TimelineRefetch
id={id}

View file

@ -8,6 +8,7 @@ import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import useResizeObserver from 'use-resize-observer/polyfilled';
import '../../mock/match_media';
import { wait } from '../../lib/helpers';
import { mockIndexPattern, TestProviders } from '../../mock';
import { useMountAppended } from '../../utils/use_mount_appended';

View file

@ -28,6 +28,7 @@ export interface OwnProps {
defaultIndices?: string[];
defaultModel: SubsetTimelineModel;
end: string;
height?: number;
id: string;
start: string;
headerFilterGroup?: React.ReactNode;
@ -48,6 +49,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
excludedRowRendererIds,
filters,
headerFilterGroup,
height,
id,
isLive,
itemsPerPage,
@ -128,6 +130,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
isLoadingIndexPattern={isLoadingIndexPattern}
filters={globalFilters}
headerFilterGroup={headerFilterGroup}
height={height}
indexPattern={indexPatterns}
isLive={isLive}
itemsPerPage={itemsPerPage!}
@ -203,6 +206,7 @@ type PropsFromRedux = ConnectedProps<typeof connector>;
export const StatefulEventsViewer = connector(
React.memo(
StatefulEventsViewerComponent,
// eslint-disable-next-line complexity
(prevProps, nextProps) =>
prevProps.id === nextProps.id &&
deepEqual(prevProps.columns, nextProps.columns) &&
@ -212,6 +216,7 @@ export const StatefulEventsViewer = connector(
prevProps.deletedEventIds === nextProps.deletedEventIds &&
prevProps.end === nextProps.end &&
deepEqual(prevProps.filters, nextProps.filters) &&
prevProps.height === nextProps.height &&
prevProps.isLive === nextProps.isLive &&
prevProps.itemsPerPage === nextProps.itemsPerPage &&
deepEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) &&

View file

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiButton, EuiWindowEvent } from '@elastic/eui';
import React, { useCallback } from 'react';
import { useFullScreen } from '../../../common/containers/use_full_screen';
import * as i18n from './translations';
export const ExitFullScreen: React.FC = () => {
const { globalFullScreen, setGlobalFullScreen } = useFullScreen();
const exitFullScreen = useCallback(() => {
setGlobalFullScreen(false);
}, [setGlobalFullScreen]);
const onKeyDown = useCallback(
(event: KeyboardEvent) => {
if (event.key === 'Escape') {
event.preventDefault();
exitFullScreen();
}
},
[exitFullScreen]
);
if (!globalFullScreen) {
return null;
}
return (
<>
<EuiWindowEvent event="keydown" handler={onKeyDown} />
<EuiButton
data-test-subj="exit-full-screen"
iconType="fullScreen"
isDisabled={!globalFullScreen}
onClick={exitFullScreen}
>
{i18n.EXIT_FULL_SCREEN}
</EuiButton>
</>
);
};

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
export const EXIT_FULL_SCREEN = i18n.translate('xpack.securitySolution.exitFullScreenButton', {
defaultMessage: 'Exit full screen',
});

View file

@ -9,6 +9,7 @@ import React from 'react';
import { Sticky } from 'react-sticky';
import styled, { css } from 'styled-components';
import { FILTERS_GLOBAL_HEIGHT } from '../../../../common/constants';
import { gutterTimeline } from '../../lib/helpers';
const offsetChrome = 49;
@ -17,6 +18,7 @@ const disableSticky = `screen and (max-width: ${euiLightVars.euiBreakpoints.s})`
const disableStickyMq = window.matchMedia(disableSticky);
const Wrapper = styled.aside<{ isSticky?: boolean }>`
height: ${FILTERS_GLOBAL_HEIGHT}px;
position: relative;
z-index: ${({ theme }) => theme.eui.euiZNavigation};
background: ${({ theme }) => theme.eui.euiColorEmptyShade};

View file

@ -17,17 +17,19 @@ import { MlPopover } from '../ml_popover/ml_popover';
import { SiemNavigation } from '../navigation';
import * as i18n from './translations';
import { useWithSource } from '../../containers/source';
import { useFullScreen } from '../../containers/use_full_screen';
import { useGetUrlSearch } from '../navigation/use_get_url_search';
import { useKibana } from '../../lib/kibana';
import { APP_ID, ADD_DATA_PATH, APP_DETECTIONS_PATH } from '../../../../common/constants';
import { LinkAnchor } from '../links';
const Wrapper = styled.header`
${({ theme }) => css`
const Wrapper = styled.header<{ show: boolean }>`
${({ show, theme }) => css`
background: ${theme.eui.euiColorEmptyShade};
border-bottom: ${theme.eui.euiBorderThin};
padding: ${theme.eui.paddingSizes.m} ${gutterTimeline} ${theme.eui.paddingSizes.m}
${theme.eui.paddingSizes.l};
${show ? '' : 'display: none;'};
`}
`;
Wrapper.displayName = 'Wrapper';
@ -42,6 +44,7 @@ interface HeaderGlobalProps {
}
export const HeaderGlobal = React.memo<HeaderGlobalProps>(({ hideDetectionEngine = false }) => {
const { indicesExist } = useWithSource();
const { globalFullScreen } = useFullScreen();
const search = useGetUrlSearch(navTabs.overview);
const { navigateToApp } = useKibana().services.application;
const goToOverview = useCallback(
@ -53,7 +56,7 @@ export const HeaderGlobal = React.memo<HeaderGlobalProps>(({ hideDetectionEngine
);
return (
<Wrapper className="siemHeaderGlobal">
<Wrapper className="siemHeaderGlobal" show={!globalFullScreen}>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" wrap>
<>
<FlexItem>

View file

@ -7,6 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../mock/match_media';
import { TestProviders } from '../../mock';
import { EditableTitle } from './editable_title';
import { useMountAppended } from '../../utils/use_mount_appended';

View file

@ -8,6 +8,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
import { shallow } from 'enzyme';
import React from 'react';
import '../../mock/match_media';
import { TestProviders } from '../../mock';
import { HeaderPage } from './index';
import { useMountAppended } from '../../utils/use_mount_appended';

View file

@ -7,6 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../mock/match_media';
import { TestProviders } from '../../mock';
import { Title } from './title';
import { useMountAppended } from '../../utils/use_mount_appended';

View file

@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`HeaderSection it renders 1`] = `
<Header>
<Header
data-test-subj="header-section"
>
<EuiFlexGroup
alignItems="center"
>

View file

@ -13,12 +13,18 @@ import { Subtitle } from '../subtitle';
interface HeaderProps {
border?: boolean;
height?: number;
}
const Header = styled.header.attrs(() => ({
className: 'siemHeaderSection',
}))<HeaderProps>`
margin-bottom: ${({ theme }) => theme.eui.euiSizeL};
${({ height }) =>
height &&
css`
height: ${height}px;
`}
margin-bottom: ${({ height, theme }) => (height ? 0 : theme.eui.euiSizeL)};
user-select: text;
${({ border }) =>
@ -32,6 +38,7 @@ Header.displayName = 'Header';
export interface HeaderSectionProps extends HeaderProps {
children?: React.ReactNode;
height?: number;
id?: string;
split?: boolean;
subtitle?: string | React.ReactNode;
@ -43,6 +50,7 @@ export interface HeaderSectionProps extends HeaderProps {
const HeaderSectionComponent: React.FC<HeaderSectionProps> = ({
border,
children,
height,
id,
split,
subtitle,
@ -50,7 +58,7 @@ const HeaderSectionComponent: React.FC<HeaderSectionProps> = ({
titleSize = 'm',
tooltip,
}) => (
<Header border={border}>
<Header data-test-subj="header-section" border={border} height={height}>
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiFlexGroup alignItems="center" responsive={false}>

View file

@ -6,6 +6,8 @@
import React from 'react';
import { shallow } from 'enzyme';
import '../../mock/match_media';
import { EntityDraggableComponent } from './entity_draggable';
import { TestProviders } from '../../mock/test_providers';
import { useMountAppended } from '../../utils/use_mount_appended';

View file

@ -7,6 +7,8 @@
import { shallow } from 'enzyme';
import { cloneDeep } from 'lodash/fp';
import React from 'react';
import '../../../mock/match_media';
import { AnomalyScoreComponent } from './anomaly_score';
import { mockAnomalies } from '../mock';
import { TestProviders } from '../../../mock/test_providers';

View file

@ -7,6 +7,8 @@
import { shallow } from 'enzyme';
import { cloneDeep } from 'lodash/fp';
import React from 'react';
import '../../../mock/match_media';
import { AnomalyScoresComponent, createJobKey } from './anomaly_scores';
import { mockAnomalies } from '../mock';
import { TestProviders } from '../../../mock/test_providers';

View file

@ -5,10 +5,12 @@
*/
import React from 'react';
import { mockAnomalies } from '../mock';
import { cloneDeep } from 'lodash/fp';
import { shallow } from 'enzyme';
import '../../../mock/match_media';
import { DraggableScoreComponent } from './draggable_score';
import { mockAnomalies } from '../mock';
describe('draggable_score', () => {
let anomalies = cloneDeep(mockAnomalies);

View file

@ -4,13 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import '../../../mock/match_media';
import { getAnomaliesHostTableColumnsCurated } from './get_anomalies_host_table_columns';
import { HostsType } from '../../../../hosts/store/model';
import * as i18n from './translations';
import { AnomaliesByHost, Anomaly } from '../types';
import { Columns } from '../../paginated_table';
import { TestProviders } from '../../../mock';
import React from 'react';
import { useMountAppended } from '../../../utils/use_mount_appended';
const startDate = new Date(2001).toISOString();

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import '../../../mock/match_media';
import { getAnomaliesNetworkTableColumnsCurated } from './get_anomalies_network_table_columns';
import { NetworkType } from '../../../../network/store/model';
import * as i18n from './translations';

View file

@ -7,11 +7,13 @@
import { EuiBadge, EuiDescriptionList, EuiFlexGroup, EuiIcon, EuiPage } from '@elastic/eui';
import styled, { createGlobalStyle } from 'styled-components';
import { FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../common/constants';
/*
SIDE EFFECT: the following `createGlobalStyle` overrides default styling in angular code that was not theme-friendly
and `EuiPopover`, `EuiToolTip` global styles
*/
export const AppGlobalStyle = createGlobalStyle`
export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimary: string } } }>`
/* dirty hack to fix draggables with tooltip on FF */
body#siem-app {
position: static;
@ -57,6 +59,10 @@ export const AppGlobalStyle = createGlobalStyle`
z-index: 9950;
}
/** applies a "toggled" button style to the Full Screen button */
.${FULL_SCREEN_TOGGLED_CLASS_NAME} {
${({ theme }) => `background-color: ${theme.eui.euiColorPrimary} !important`};
}
`;
export const DescriptionListStyled = styled(EuiDescriptionList)`

View file

@ -4,14 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import '../../mock/match_media';
import {
getRowItemDraggables,
getRowItemOverflow,
getRowItemDraggable,
OverflowFieldComponent,
} from './helpers';
import React from 'react';
import { shallow } from 'enzyme';
import { TestProviders } from '../../mock';
import { getEmptyValue } from '../empty_value';
import { useMountAppended } from '../../utils/use_mount_appended';

View file

@ -7,6 +7,7 @@
import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import '../../mock/match_media';
import { mockBrowserFields } from '../../containers/source/mock';
import {
apolloClientObservable,

View file

@ -7,6 +7,7 @@
import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import '../../mock/match_media';
import { TestProviders, mockIndexPattern } from '../../mock';
import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions';

View file

@ -5,9 +5,10 @@
*/
import classNames from 'classnames';
import React from 'react';
import React, { useEffect } from 'react';
import styled from 'styled-components';
import { useFullScreen } from '../../containers/use_full_screen';
import { gutterTimeline } from '../../lib/helpers';
import { AppGlobalStyle } from '../page/index';
@ -45,6 +46,11 @@ const WrapperPageComponent: React.FC<WrapperPageProps> = ({
style,
noPadding,
}) => {
const { setGlobalFullScreen } = useFullScreen();
useEffect(() => {
setGlobalFullScreen(false); // exit full screen mode on page load
}, [setGlobalFullScreen]);
const classes = classNames(className, {
siemWrapperPage: true,
'siemWrapperPage--restrictWidthDefault':

View file

@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { inputsSelectors } from '../../store';
import { inputsActions } from '../../store/actions';
export const useFullScreen = () => {
const dispatch = useDispatch();
const globalFullScreen = useSelector(inputsSelectors.globalFullScreenSelector) ?? false;
const timelineFullScreen = useSelector(inputsSelectors.timelineFullScreenSelector) ?? false;
const setGlobalFullScreen = useCallback(
(fullScreen: boolean) => dispatch(inputsActions.setFullScreen({ id: 'global', fullScreen })),
[dispatch]
);
const setTimelineFullScreen = useCallback(
(fullScreen: boolean) => dispatch(inputsActions.setFullScreen({ id: 'timeline', fullScreen })),
[dispatch]
);
const memoizedReturn = useMemo(
() => ({
globalFullScreen,
setGlobalFullScreen,
setTimelineFullScreen,
timelineFullScreen,
}),
[globalFullScreen, setGlobalFullScreen, setTimelineFullScreen, timelineFullScreen]
);
return memoizedReturn;
};

View file

@ -37,6 +37,11 @@ export const startAutoReload = actionCreator<{ id: InputsModelId }>('START_KQL_A
export const stopAutoReload = actionCreator<{ id: InputsModelId }>('STOP_KQL_AUTO_RELOAD');
export const setFullScreen = actionCreator<{
id: InputsModelId;
fullScreen: boolean;
}>('SET_FULL_SCREEN');
export const setQuery = actionCreator<{
inputId: InputsModelId;
id: string;

View file

@ -9,6 +9,22 @@ import { get } from 'lodash/fp';
import { InputsModel, TimeRange, Refetch, RefetchKql, InspectQuery } from './model';
import { InputsModelId } from './constants';
export const updateInputFullScreen = (
inputId: InputsModelId,
fullScreen: boolean,
state: InputsModel
): InputsModel => ({
...state,
global: {
...state.global,
fullScreen: inputId === 'global' ? fullScreen : state.global.fullScreen,
},
timeline: {
...state.timeline,
fullScreen: inputId === 'timeline' ? fullScreen : state.timeline.fullScreen,
},
});
export const updateInputTimerange = (
inputId: InputsModelId,
timerange: TimeRange,

View file

@ -80,6 +80,7 @@ export interface InputsRange {
query: Query;
filters: Filter[];
savedQuery?: SavedQuery;
fullScreen?: boolean;
}
export interface LinkTo {

View file

@ -12,6 +12,7 @@ import {
deleteAllQuery,
setAbsoluteRangeDatePicker,
setDuration,
setFullScreen,
setInspectionParameter,
setQuery,
setRelativeRangeDatePicker,
@ -38,6 +39,7 @@ import {
removeTimelineLink,
addTimelineLink,
deleteOneQuery as helperDeleteOneQuery,
updateInputFullScreen,
} from './helpers';
import { InputsModel, TimeRange } from './model';
@ -57,6 +59,7 @@ export const initialInputsState: InputsState = {
language: 'kuery',
},
filters: [],
fullScreen: false,
},
timeline: {
timerange: {
@ -71,6 +74,7 @@ export const initialInputsState: InputsState = {
language: 'kuery',
},
filters: [],
fullScreen: false,
},
};
@ -98,6 +102,7 @@ export const createInitialInputsState = (): InputsState => {
language: 'kuery',
},
filters: [],
fullScreen: false,
},
timeline: {
timerange: {
@ -118,6 +123,7 @@ export const createInitialInputsState = (): InputsState => {
language: 'kuery',
},
filters: [],
fullScreen: false,
},
};
};
@ -163,6 +169,9 @@ export const inputsReducer = reducerWithInitialState(initialInputsState)
};
return updateInputTimerange(id, timerange, state);
})
.case(setFullScreen, (state, { id, fullScreen }) => {
return updateInputFullScreen(id, fullScreen, state);
})
.case(deleteAllQuery, (state, { id }) => ({
...state,
[id]: {

View file

@ -44,6 +44,13 @@ export const timelineTimeRangeSelector = createSelector(
(timeline) => timeline.timerange
);
export const globalFullScreenSelector = createSelector(selectGlobal, (global) => global.fullScreen);
export const timelineFullScreenSelector = createSelector(
selectTimeline,
(timeline) => timeline.fullScreen
);
export const globalTimeRangeSelector = createSelector(selectGlobal, (global) => global.timerange);
export const globalPolicySelector = createSelector(selectGlobal, (global) => global.policy);

View file

@ -7,6 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import '../../../common/mock/match_media';
import { AlertsHistogram } from './alerts_histogram';
jest.mock('../../../common/lib/kibana');

View file

@ -7,6 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import '../../../common/mock/match_media';
import { AlertsHistogramPanel } from './index';
jest.mock('react-router-dom', () => {

View file

@ -7,6 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import '../../../common/mock/match_media';
import { TimelineId } from '../../../../common/types/timeline';
import { TestProviders } from '../../../common/mock';
import { AlertsTableComponent } from './index';

View file

@ -61,6 +61,7 @@ interface OwnProps {
timelineId: TimelineIdLiteral;
canUserCRUD: boolean;
defaultFilters?: Filter[];
eventsViewerBodyHeight?: number;
hasIndexWrite: boolean;
from: string;
loading: boolean;
@ -86,6 +87,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
clearEventsLoading,
clearSelected,
defaultFilters,
eventsViewerBodyHeight,
from,
globalFilters,
globalQuery,
@ -443,6 +445,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
defaultModel={alertsDefaultModel}
end={to}
headerFilterGroup={headerFilterGroup}
height={eventsViewerBodyHeight}
id={timelineId}
start={from}
utilityBar={utilityBarCallback}

View file

@ -7,6 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import '../../../common/mock/match_media';
import { DetectionEngineHeaderPage } from './index';
describe('detection_engine_header_page', () => {

View file

@ -7,6 +7,7 @@
import React, { useRef } from 'react';
import { shallow } from 'enzyme';
import '../../../../common/mock/match_media';
import { AllRulesTables } from './index';
import { AllRulesTabs } from '../../../pages/detection_engine/rules/all';

View file

@ -7,6 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import '../../../../common/mock/match_media';
import { PrePackagedRulesPrompt } from './load_empty_prompt';
jest.mock('react-router-dom', () => {

View file

@ -5,15 +5,33 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';
import { useParams } from 'react-router-dom';
import '../../../common/mock/match_media';
import {
apolloClientObservable,
createSecuritySolutionStorageMock,
kibanaObservable,
mockGlobalState,
TestProviders,
SUB_PLUGINS_REDUCER,
} from '../../../common/mock';
import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions';
import { DetectionEnginePageComponent } from './detection_engine';
import { useUserInfo } from '../../components/user_info';
import { useWithSource } from '../../../common/containers/source';
import { createStore, State } from '../../../common/store';
import { mockHistory, Router } from '../../../cases/components/__mock__/router';
// Test will fail because we will to need to mock some core services to make the test work
// For now let's forget about SiemSearchBar and QueryBar
jest.mock('../../../common/components/search_bar', () => ({
SiemSearchBar: () => null,
}));
jest.mock('../../../common/components/query_bar', () => ({
QueryBar: () => null,
}));
jest.mock('../../containers/detection_engine/lists/use_lists_config');
jest.mock('../../components/user_info');
jest.mock('../../../common/containers/source');
@ -36,6 +54,19 @@ jest.mock('react-router-dom', () => {
};
});
const state: State = {
...mockGlobalState,
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
apolloClientObservable,
kibanaObservable,
storage
);
describe('DetectionEnginePageComponent', () => {
beforeAll(() => {
(useParams as jest.Mock).mockReturnValue({});
@ -47,14 +78,18 @@ describe('DetectionEnginePageComponent', () => {
});
it('renders correctly', () => {
const wrapper = shallow(
<DetectionEnginePageComponent
query={{ query: 'query', language: 'language' }}
filters={[]}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
/>
const wrapper = mount(
<TestProviders store={store}>
<Router history={mockHistory}>
<DetectionEnginePageComponent
query={{ query: 'query', language: 'language' }}
filters={[]}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
/>
</Router>
</TestProviders>
);
expect(wrapper.find('FiltersGlobal')).toHaveLength(1);
expect(wrapper.find('FiltersGlobal').exists()).toBe(true);
});
});

View file

@ -4,12 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiSpacer } from '@elastic/eui';
import { EuiSpacer, EuiWindowEvent } from '@elastic/eui';
import { noop } from 'lodash/fp';
import React, { useCallback, useMemo, useState } from 'react';
import { StickyContainer } from 'react-sticky';
import { connect, ConnectedProps } from 'react-redux';
import { useWindowSize } from 'react-use';
import { useHistory } from 'react-router-dom';
import { globalHeaderHeightPx } from '../../../app/home';
import { SecurityPageName } from '../../../app/types';
import { TimelineId } from '../../../../common/types/timeline';
import { useGlobalTime } from '../../../common/containers/use_global_time';
@ -31,6 +34,7 @@ import { NoWriteAlertsCallOut } from '../../components/no_write_alerts_callout';
import { AlertsHistogramPanel } from '../../components/alerts_histogram_panel';
import { alertsHistogramOptions } from '../../components/alerts_histogram_panel/config';
import { useUserInfo } from '../../components/user_info';
import { EVENTS_VIEWER_HEADER_HEIGHT } from '../../../common/components/events_viewer/events_viewer';
import { OverviewEmpty } from '../../../overview/components/overview_empty';
import { DetectionEngineNoIndex } from './detection_engine_no_signal_index';
import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page';
@ -39,6 +43,14 @@ import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unau
import * as i18n from './translations';
import { LinkButton } from '../../../common/components/links';
import { useFormatUrl } from '../../../common/components/link_to';
import { FILTERS_GLOBAL_HEIGHT } from '../../../../common/constants';
import { useFullScreen } from '../../../common/containers/use_full_screen';
import { Display } from '../../../hosts/pages/display';
import {
getEventsViewerBodyHeight,
MIN_EVENTS_VIEWER_BODY_HEIGHT,
} from '../../../timelines/components/timeline/body/helpers';
import { footerHeight } from '../../../timelines/components/timeline/footer';
import { buildShowBuildingBlockFilter } from '../../components/alerts_table/default_config';
export const DetectionEnginePageComponent: React.FC<PropsFromRedux> = ({
@ -47,6 +59,8 @@ export const DetectionEnginePageComponent: React.FC<PropsFromRedux> = ({
setAbsoluteRangeDatePicker,
}) => {
const { to, from, deleteQuery, setQuery } = useGlobalTime();
const { height: windowHeight } = useWindowSize();
const { globalFullScreen } = useFullScreen();
const {
loading: userInfoLoading,
isSignalIndexExists,
@ -136,51 +150,66 @@ export const DetectionEnginePageComponent: React.FC<PropsFromRedux> = ({
{hasIndexWrite != null && !hasIndexWrite && <NoWriteAlertsCallOut />}
{indicesExist ? (
<StickyContainer>
<EuiWindowEvent event="resize" handler={noop} />
<FiltersGlobal>
<SiemSearchBar id="global" indexPattern={indexPattern} />
</FiltersGlobal>
<WrapperPage>
<DetectionEngineHeaderPage
subtitle={
lastAlerts != null && (
<>
{i18n.LAST_ALERT}
{': '}
{lastAlerts}
</>
)
}
title={i18n.PAGE_TITLE}
>
<LinkButton
fill
onClick={goToRules}
href={formatUrl(getRulesUrl())}
iconType="gear"
data-test-subj="manage-alert-detection-rules"
>
{i18n.BUTTON_MANAGE_RULES}
</LinkButton>
</DetectionEngineHeaderPage>
<AlertsHistogramPanel
deleteQuery={deleteQuery}
filters={alertsHistogramDefaultFilters}
from={from}
query={query}
setQuery={setQuery}
showTotalAlertsCount={true}
signalIndexName={signalIndexName}
stackByOptions={alertsHistogramOptions}
to={to}
updateDateRange={updateDateRangeCallback}
/>
<EuiSpacer size="l" />
<WrapperPage noPadding={globalFullScreen}>
<Display show={!globalFullScreen}>
<DetectionEngineHeaderPage
subtitle={
lastAlerts != null && (
<>
{i18n.LAST_ALERT}
{': '}
{lastAlerts}
</>
)
}
title={i18n.PAGE_TITLE}
>
<LinkButton
fill
onClick={goToRules}
href={formatUrl(getRulesUrl())}
iconType="gear"
data-test-subj="manage-alert-detection-rules"
>
{i18n.BUTTON_MANAGE_RULES}
</LinkButton>
</DetectionEngineHeaderPage>
<AlertsHistogramPanel
deleteQuery={deleteQuery}
filters={alertsHistogramDefaultFilters}
from={from}
query={query}
setQuery={setQuery}
showTotalAlertsCount={true}
signalIndexName={signalIndexName}
stackByOptions={alertsHistogramOptions}
to={to}
updateDateRange={updateDateRangeCallback}
/>
<EuiSpacer size="l" />
</Display>
<AlertsTable
timelineId={TimelineId.detectionsPage}
loading={loading}
hasIndexWrite={hasIndexWrite ?? false}
canUserCRUD={(canUserCRUD ?? false) && (hasEncryptionKey ?? false)}
eventsViewerBodyHeight={
globalFullScreen
? getEventsViewerBodyHeight({
footerHeight,
headerHeight: EVENTS_VIEWER_HEADER_HEIGHT,
kibanaChromeHeight: globalHeaderHeightPx,
otherContentHeight: FILTERS_GLOBAL_HEIGHT,
windowHeight,
})
: MIN_EVENTS_VIEWER_BODY_HEIGHT
}
from={from}
defaultFilters={alertsTableDefaultFilters}
showBuildingBlockAlerts={showBuildingBlockAlerts}

View file

@ -9,6 +9,7 @@ import { createMemoryHistory } from 'history';
const history = createMemoryHistory();
import '../../../../../common/mock/match_media';
import { mockRule } from './__mocks__/mock';
import { getActions } from './columns';

View file

@ -8,6 +8,7 @@ import React from 'react';
import { shallow, mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import '../../../../../common/mock/match_media';
import { createKibanaContextProviderMock } from '../../../../../common/mock/kibana_react';
import { TestProviders } from '../../../../../common/mock';
import { wait } from '../../../../../common/lib/helpers';

View file

@ -7,6 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import '../../../../../common/mock/match_media';
import { TestProviders } from '../../../../../common/mock';
import { CreateRulePage } from './index';
import { useUserInfo } from '../../../../components/user_info';

View file

@ -5,16 +5,33 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';
import '../../../../../common/mock/match_media';
import { TestProviders } from '../../../../../common/mock';
import {
apolloClientObservable,
createSecuritySolutionStorageMock,
kibanaObservable,
mockGlobalState,
TestProviders,
SUB_PLUGINS_REDUCER,
} from '../../../../../common/mock';
import { RuleDetailsPageComponent } from './index';
import { createStore, State } from '../../../../../common/store';
import { setAbsoluteRangeDatePicker } from '../../../../../common/store/inputs/actions';
import { useUserInfo } from '../../../../components/user_info';
import { useWithSource } from '../../../../../common/containers/source';
import { useParams } from 'react-router-dom';
import { mockHistory, Router } from '../../../../../cases/components/__mock__/router';
// Test will fail because we will to need to mock some core services to make the test work
// For now let's forget about SiemSearchBar and QueryBar
jest.mock('../../../../../common/components/search_bar', () => ({
SiemSearchBar: () => null,
}));
jest.mock('../../../../../common/components/query_bar', () => ({
QueryBar: () => null,
}));
jest.mock('../../../../containers/detection_engine/lists/use_lists_config');
jest.mock('../../../../../common/components/link_to');
jest.mock('../../../../components/user_info');
@ -38,6 +55,18 @@ jest.mock('react-router-dom', () => {
};
});
const state: State = {
...mockGlobalState,
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
apolloClientObservable,
kibanaObservable,
storage
);
describe('RuleDetailsPageComponent', () => {
beforeAll(() => {
(useUserInfo as jest.Mock).mockReturnValue({});
@ -49,17 +78,21 @@ describe('RuleDetailsPageComponent', () => {
});
it('renders correctly', () => {
const wrapper = shallow(
<RuleDetailsPageComponent
query={{ query: '', language: 'language' }}
filters={[]}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
/>,
const wrapper = mount(
<TestProviders store={store}>
<Router history={mockHistory}>
<RuleDetailsPageComponent
query={{ query: '', language: 'language' }}
filters={[]}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
/>
</Router>
</TestProviders>,
{
wrappingComponent: TestProviders,
}
);
expect(wrapper.find('DetectionEngineHeaderPage')).toHaveLength(1);
expect(wrapper.find('[data-test-subj="header-page-title"]').exists()).toBe(true);
});
});

View file

@ -15,13 +15,17 @@ import {
EuiTab,
EuiTabs,
EuiToolTip,
EuiWindowEvent,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { noop } from 'lodash/fp';
import React, { FC, memo, useCallback, useEffect, useMemo, useState } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { StickyContainer } from 'react-sticky';
import { connect, ConnectedProps } from 'react-redux';
import { useWindowSize } from 'react-use';
import { globalHeaderHeightPx } from '../../../../../app/home';
import { TimelineId } from '../../../../../../common/types/timeline';
import { UpdateDateRange } from '../../../../../common/components/charts/common';
import { FiltersGlobal } from '../../../../../common/components/filters_global';
@ -62,6 +66,7 @@ import * as ruleI18n from '../translations';
import * as i18n from './translations';
import { useGlobalTime } from '../../../../../common/containers/use_global_time';
import { alertsHistogramOptions } from '../../../../components/alerts_histogram_panel/config';
import { EVENTS_VIEWER_HEADER_HEIGHT } from '../../../../../common/components/events_viewer/events_viewer';
import { inputsSelectors } from '../../../../../common/store/inputs';
import { State } from '../../../../../common/store';
import { InputsRange } from '../../../../../common/store/inputs/model';
@ -76,7 +81,15 @@ import { SecurityPageName } from '../../../../../app/types';
import { LinkButton } from '../../../../../common/components/links';
import { useFormatUrl } from '../../../../../common/components/link_to';
import { ExceptionsViewer } from '../../../../../common/components/exceptions/viewer';
import { FILTERS_GLOBAL_HEIGHT } from '../../../../../../common/constants';
import { useFullScreen } from '../../../../../common/containers/use_full_screen';
import { Display } from '../../../../../hosts/pages/display';
import { ExceptionListTypeEnum, ExceptionIdentifiers } from '../../../../../lists_plugin_deps';
import {
getEventsViewerBodyHeight,
MIN_EVENTS_VIEWER_BODY_HEIGHT,
} from '../../../../../timelines/components/timeline/body/helpers';
import { footerHeight } from '../../../../../timelines/components/timeline/footer';
enum RuleDetailTabs {
alerts = 'alerts',
@ -141,6 +154,8 @@ export const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
const mlCapabilities = useMlCapabilities();
const history = useHistory();
const { formatUrl } = useFormatUrl(SecurityPageName.detections);
const { height: windowHeight } = useWindowSize();
const { globalFullScreen } = useFullScreen();
// TODO: Refactor license check + hasMlAdminPermissions to common check
const hasMlPermissions =
@ -329,140 +344,156 @@ export const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
{userHasNoPermissions(canUserCRUD) && <ReadOnlyCallOut />}
{indicesExist ? (
<StickyContainer>
<EuiWindowEvent event="resize" handler={noop} />
<FiltersGlobal>
<SiemSearchBar id="global" indexPattern={indexPattern} />
</FiltersGlobal>
<WrapperPage>
<DetectionEngineHeaderPage
backOptions={{
href: getRulesUrl(),
text: i18n.BACK_TO_RULES,
pageId: SecurityPageName.detections,
}}
border
subtitle={subTitle}
subtitle2={[
...(lastAlerts != null
? [
<>
{detectionI18n.LAST_ALERT}
{': '}
{lastAlerts}
</>,
]
: []),
<RuleStatus ruleId={ruleId ?? null} ruleEnabled={ruleEnabled} />,
]}
title={title}
>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiToolTip
position="top"
content={
rule?.type === 'machine_learning' && !hasMlPermissions
? detectionI18n.ML_RULES_DISABLED_MESSAGE
: undefined
}
>
<RuleSwitch
id={rule?.id ?? '-1'}
isDisabled={
userHasNoPermissions(canUserCRUD) || (!hasMlPermissions && !rule?.enabled)
<WrapperPage noPadding={globalFullScreen}>
<Display show={!globalFullScreen}>
<DetectionEngineHeaderPage
backOptions={{
href: getRulesUrl(),
text: i18n.BACK_TO_RULES,
pageId: SecurityPageName.detections,
}}
border
subtitle={subTitle}
subtitle2={[
...(lastAlerts != null
? [
<>
{detectionI18n.LAST_ALERT}
{': '}
{lastAlerts}
</>,
]
: []),
<RuleStatus ruleId={ruleId ?? null} ruleEnabled={ruleEnabled} />,
]}
title={title}
>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiToolTip
position="top"
content={
rule?.type === 'machine_learning' && !hasMlPermissions
? detectionI18n.ML_RULES_DISABLED_MESSAGE
: undefined
}
enabled={rule?.enabled ?? false}
optionLabel={i18n.ACTIVATE_RULE}
onChange={handleOnChangeEnabledRule}
/>
</EuiToolTip>
>
<RuleSwitch
id={rule?.id ?? '-1'}
isDisabled={
userHasNoPermissions(canUserCRUD) || (!hasMlPermissions && !rule?.enabled)
}
enabled={rule?.enabled ?? false}
optionLabel={i18n.ACTIVATE_RULE}
onChange={handleOnChangeEnabledRule}
/>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
<EuiFlexItem grow={false}>
<LinkButton
onClick={goToEditRule}
iconType="controlsHorizontal"
isDisabled={userHasNoPermissions(canUserCRUD) ?? true}
href={formatUrl(getEditRuleUrl(ruleId ?? ''))}
>
{ruleI18n.EDIT_RULE_SETTINGS}
</LinkButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={userHasNoPermissions(canUserCRUD)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</DetectionEngineHeaderPage>
{ruleError}
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem data-test-subj="aboutRule" component="section" grow={1}>
<StepAboutRuleToggleDetails
loading={isLoading}
stepData={aboutRuleData}
stepDataDetails={modifiedAboutRuleDetailsData}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
<EuiFlexItem grow={false}>
<LinkButton
onClick={goToEditRule}
iconType="controlsHorizontal"
isDisabled={userHasNoPermissions(canUserCRUD) ?? true}
href={formatUrl(getEditRuleUrl(ruleId ?? ''))}
>
{ruleI18n.EDIT_RULE_SETTINGS}
</LinkButton>
<EuiFlexItem grow={1}>
<EuiFlexGroup direction="column">
<EuiFlexItem component="section" grow={1}>
<StepPanel loading={isLoading} title={ruleI18n.DEFINITION}>
{defineRuleData != null && (
<StepDefineRule
descriptionColumns="singleSplit"
isReadOnlyView={true}
isLoading={false}
defaultValues={defineRuleData}
/>
)}
</StepPanel>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={userHasNoPermissions(canUserCRUD)}
/>
<EuiSpacer />
<EuiFlexItem data-test-subj="schedule" component="section" grow={1}>
<StepPanel loading={isLoading} title={ruleI18n.SCHEDULE}>
{scheduleRuleData != null && (
<StepScheduleRule
descriptionColumns="singleSplit"
isReadOnlyView={true}
isLoading={false}
defaultValues={scheduleRuleData}
/>
)}
</StepPanel>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</DetectionEngineHeaderPage>
{ruleError}
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem data-test-subj="aboutRule" component="section" grow={1}>
<StepAboutRuleToggleDetails
loading={isLoading}
stepData={aboutRuleData}
stepDataDetails={modifiedAboutRuleDetailsData}
/>
</EuiFlexItem>
<EuiFlexItem grow={1}>
<EuiFlexGroup direction="column">
<EuiFlexItem component="section" grow={1}>
<StepPanel loading={isLoading} title={ruleI18n.DEFINITION}>
{defineRuleData != null && (
<StepDefineRule
descriptionColumns="singleSplit"
isReadOnlyView={true}
isLoading={false}
defaultValues={defineRuleData}
/>
)}
</StepPanel>
</EuiFlexItem>
<EuiSpacer />
<EuiFlexItem data-test-subj="schedule" component="section" grow={1}>
<StepPanel loading={isLoading} title={ruleI18n.SCHEDULE}>
{scheduleRuleData != null && (
<StepScheduleRule
descriptionColumns="singleSplit"
isReadOnlyView={true}
isLoading={false}
defaultValues={scheduleRuleData}
/>
)}
</StepPanel>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
{tabs}
<EuiSpacer />
<EuiSpacer />
{tabs}
<EuiSpacer />
</Display>
{ruleDetailTab === RuleDetailTabs.alerts && (
<>
<AlertsHistogramPanel
deleteQuery={deleteQuery}
filters={alertMergedFilters}
query={query}
from={from}
signalIndexName={signalIndexName}
setQuery={setQuery}
stackByOptions={alertsHistogramOptions}
to={to}
updateDateRange={updateDateRangeCallback}
/>
<EuiSpacer />
<Display show={!globalFullScreen}>
<AlertsHistogramPanel
deleteQuery={deleteQuery}
filters={alertMergedFilters}
query={query}
from={from}
signalIndexName={signalIndexName}
setQuery={setQuery}
stackByOptions={alertsHistogramOptions}
to={to}
updateDateRange={updateDateRangeCallback}
/>
<EuiSpacer />
</Display>
{ruleId != null && (
<AlertsTable
timelineId={TimelineId.detectionsRulesDetailsPage}
canUserCRUD={canUserCRUD ?? false}
defaultFilters={alertDefaultFilters}
eventsViewerBodyHeight={
globalFullScreen
? getEventsViewerBodyHeight({
footerHeight,
headerHeight: EVENTS_VIEWER_HEADER_HEIGHT,
kibanaChromeHeight: globalHeaderHeightPx,
otherContentHeight: FILTERS_GLOBAL_HEIGHT,
windowHeight,
})
: MIN_EVENTS_VIEWER_BODY_HEIGHT
}
hasIndexWrite={hasIndexWrite ?? false}
from={from}
loading={loading}

View file

@ -7,6 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import '../../../../../common/mock/match_media';
import { TestProviders } from '../../../../../common/mock';
import { EditRulePage } from './index';
import { useUserInfo } from '../../../../components/user_info';

View file

@ -7,6 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import '../../../../common/mock/match_media';
import { RulesPage } from './index';
import { useUserInfo } from '../../../components/user_info';
import { usePrePackagedRules } from '../../../containers/detection_engine/rules';

View file

@ -9,6 +9,7 @@ import { getOr } from 'lodash/fp';
import React from 'react';
import { Provider as ReduxStoreProvider } from 'react-redux';
import '../../../common/mock/match_media';
import {
apolloClientObservable,
mockGlobalState,

View file

@ -9,6 +9,7 @@ import { getOr } from 'lodash/fp';
import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import '../../../common/mock/match_media';
import {
apolloClientObservable,
mockIndexPattern,

View file

@ -7,6 +7,7 @@
import { mockKpiHostsData, mockKpiHostDetailsData } from './mock';
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import '../../../common/mock/match_media';
import { KpiHostsComponentBase } from '.';
import * as statItems from '../../../common/components/stat_items';
import { kpiHostsMapping } from './kpi_hosts_mapping';

View file

@ -9,6 +9,7 @@ import { getOr } from 'lodash/fp';
import React from 'react';
import { TestProviders } from '../../../common/mock';
import '../../../common/mock/match_media';
import { hostsModel } from '../../store';
import { getEmptyValue } from '../../../common/components/empty_value';
import { useMountAppended } from '../../../common/utils/use_mount_appended';

View file

@ -8,6 +8,7 @@ import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import useResizeObserver from 'use-resize-observer/polyfilled';
import '../../../common/mock/match_media';
import { mockIndexPattern } from '../../../common/mock/index_pattern';
import { TestProviders } from '../../../common/mock/test_providers';
import { HostDetailsTabs } from './details_tabs';

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import styled from 'styled-components';
export const Display = styled.div<{ show: boolean }>`
${({ show }) => (show ? '' : 'display: none;')};
`;
Display.displayName = 'Display';

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiSpacer } from '@elastic/eui';
import { EuiSpacer, EuiWindowEvent } from '@elastic/eui';
import { noop } from 'lodash/fp';
import React, { useCallback } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { StickyContainer } from 'react-sticky';
@ -22,6 +23,7 @@ import { manageQuery } from '../../common/components/page/manage_query';
import { SiemSearchBar } from '../../common/components/search_bar';
import { WrapperPage } from '../../common/components/wrapper_page';
import { KpiHostsQuery } from '../containers/kpi_hosts';
import { useFullScreen } from '../../common/containers/use_full_screen';
import { useGlobalTime } from '../../common/containers/use_global_time';
import { useWithSource } from '../../common/containers/source';
import { LastEventIndexKey } from '../../graphql/types';
@ -34,6 +36,7 @@ import { SpyRoute } from '../../common/utils/route/spy_routes';
import { esQuery } from '../../../../../../src/plugins/data/public';
import { useMlCapabilities } from '../../common/components/ml_popover/hooks/use_ml_capabilities';
import { OverviewEmpty } from '../../overview/components/overview_empty';
import { Display } from './display';
import { HostsTabs } from './hosts_tabs';
import { navTabsHosts } from './nav_tabs';
import * as i18n from './translations';
@ -47,6 +50,7 @@ const KpiHostsComponentManage = manageQuery(KpiHostsComponent);
export const HostsComponent = React.memo<HostsComponentProps & PropsFromRedux>(
({ filters, query, setAbsoluteRangeDatePicker, hostsPagePath }) => {
const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime();
const { globalFullScreen } = useFullScreen();
const capabilities = useMlCapabilities();
const kibana = useKibana();
const { tabName } = useParams();
@ -88,44 +92,47 @@ export const HostsComponent = React.memo<HostsComponentProps & PropsFromRedux>(
<>
{indicesExist ? (
<StickyContainer>
<EuiWindowEvent event="resize" handler={noop} />
<FiltersGlobal>
<SiemSearchBar indexPattern={indexPattern} id="global" />
</FiltersGlobal>
<WrapperPage>
<HeaderPage
border
subtitle={<LastEventTime indexKey={LastEventIndexKey.hosts} />}
title={i18n.PAGE_TITLE}
/>
<WrapperPage noPadding={globalFullScreen}>
<Display show={!globalFullScreen}>
<HeaderPage
border
subtitle={<LastEventTime indexKey={LastEventIndexKey.hosts} />}
title={i18n.PAGE_TITLE}
/>
<KpiHostsQuery
endDate={to}
filterQuery={filterQuery}
skip={isInitializing}
sourceId="default"
startDate={from}
>
{({ kpiHosts, loading, id, inspect, refetch }) => (
<KpiHostsComponentManage
data={kpiHosts}
from={from}
id={id}
inspect={inspect}
loading={loading}
refetch={refetch}
setQuery={setQuery}
to={to}
narrowDateRange={narrowDateRange}
/>
)}
</KpiHostsQuery>
<KpiHostsQuery
endDate={to}
filterQuery={filterQuery}
skip={isInitializing}
sourceId="default"
startDate={from}
>
{({ kpiHosts, loading, id, inspect, refetch }) => (
<KpiHostsComponentManage
data={kpiHosts}
from={from}
id={id}
inspect={inspect}
loading={loading}
refetch={refetch}
setQuery={setQuery}
to={to}
narrowDateRange={narrowDateRange}
/>
)}
</KpiHostsQuery>
<EuiSpacer />
<EuiSpacer />
<SiemNavigation navTabs={navTabsHosts(hasMlUserPermissions(capabilities))} />
<SiemNavigation navTabs={navTabsHosts(hasMlUserPermissions(capabilities))} />
<EuiSpacer />
<EuiSpacer />
</Display>
<HostsTabs
deleteQuery={deleteQuery}

View file

@ -6,6 +6,8 @@
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useWindowSize } from 'react-use';
import { TimelineId } from '../../../../common/types/timeline';
import { StatefulEventsViewer } from '../../../common/components/events_viewer';
import { HostsComponentsQueryProps } from './types';
@ -16,10 +18,19 @@ import {
MatrixHisrogramConfigs,
} from '../../../common/components/matrix_histogram/types';
import { MatrixHistogramContainer } from '../../../common/components/matrix_histogram';
import { useFullScreen } from '../../../common/containers/use_full_screen';
import * as i18n from '../translations';
import { HistogramType } from '../../../graphql/types';
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
import {
getEventsViewerBodyHeight,
getInvestigateInResolverAction,
MIN_EVENTS_VIEWER_BODY_HEIGHT,
} from '../../../timelines/components/timeline/body/helpers';
import { FILTERS_GLOBAL_HEIGHT } from '../../../../common/constants';
import { globalHeaderHeightPx } from '../../../app/home';
import { EVENTS_VIEWER_HEADER_HEIGHT } from '../../../common/components/events_viewer/events_viewer';
import { footerHeight } from '../../../timelines/components/timeline/footer';
const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery';
@ -60,7 +71,8 @@ export const EventsQueryTabBody = ({
}: HostsComponentsQueryProps) => {
const { initializeTimeline } = useManageTimeline();
const dispatch = useDispatch();
const { height: windowHeight } = useWindowSize();
const { globalFullScreen } = useFullScreen();
useEffect(() => {
initializeTimeline({
id: TimelineId.hostsPageEvents,
@ -81,19 +93,32 @@ export const EventsQueryTabBody = ({
return (
<>
<MatrixHistogramContainer
endDate={endDate}
filterQuery={filterQuery}
setQuery={setQuery}
sourceId="default"
startDate={startDate}
type={hostsModel.HostsType.page}
id={EVENTS_HISTOGRAM_ID}
{...histogramConfigs}
/>
{!globalFullScreen && (
<MatrixHistogramContainer
endDate={endDate}
filterQuery={filterQuery}
setQuery={setQuery}
sourceId="default"
startDate={startDate}
type={hostsModel.HostsType.page}
id={EVENTS_HISTOGRAM_ID}
{...histogramConfigs}
/>
)}
<StatefulEventsViewer
defaultModel={eventsDefaultModel}
end={endDate}
height={
globalFullScreen
? getEventsViewerBodyHeight({
footerHeight,
headerHeight: EVENTS_VIEWER_HEADER_HEIGHT,
kibanaChromeHeight: globalHeaderHeightPx,
otherContentHeight: FILTERS_GLOBAL_HEIGHT,
windowHeight,
})
: MIN_EVENTS_VIEWER_BODY_HEIGHT
}
id={TimelineId.hostsPageEvents}
start={startDate}
pageFilters={pageFilters}

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import '../../../common/mock/match_media';
import {
DEFAULT_ICON,
EXTERNAL,

View file

@ -7,6 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../../common/mock/match_media';
import { useIndexPatterns } from '../../../common/hooks/use_index_patterns';
import { EmbeddedMapComponent } from './embedded_map';

View file

@ -6,6 +6,8 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../../../common/mock/match_media';
import { LineToolTipContentComponent } from './line_tool_tip_content';
import {
SUM_OF_CLIENT_BYTES,

View file

@ -6,6 +6,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../../../common/mock/match_media';
import { MapToolTipComponent } from './map_tool_tip';
import { TooltipFeature } from '../../../../../../maps/common/descriptor_types';

View file

@ -6,6 +6,8 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../../../common/mock/match_media';
import { getRenderedFieldValue, PointToolTipContentComponent } from './point_tool_tip_content';
import { TestProviders } from '../../../../common/mock';
import { getEmptyStringTag } from '../../../../common/components/empty_value';

View file

@ -8,6 +8,7 @@ import { mount } from 'enzyme';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import '../../../common/mock/match_media';
import { TestProviders } from '../../../common/mock';
import { FlowTargetSelectConnectedComponent } from './index';
import { FlowTarget } from '../../../graphql/types';

View file

@ -7,6 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../../common/mock/match_media';
import { TestProviders } from '../../../common/mock/test_providers';
import { useMountAppended } from '../../../common/utils/use_mount_appended';

View file

@ -9,6 +9,7 @@ import React from 'react';
import { ActionCreator } from 'typescript-fsa';
import { FlowTarget } from '../../../graphql/types';
import '../../../common/mock/match_media';
import {
apolloClientObservable,
mockGlobalState,

View file

@ -15,6 +15,7 @@ import {
kibanaObservable,
createSecuritySolutionStorageMock,
} from '../../../common/mock';
import '../../../common/mock/match_media';
import { createStore, State } from '../../../common/store';
import { KpiNetworkComponent } from '.';
import { mockData } from './mock';

View file

@ -10,6 +10,7 @@ import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { Provider as ReduxStoreProvider } from 'react-redux';
import '../../../common/mock/match_media';
import {
apolloClientObservable,
mockGlobalState,

View file

@ -10,6 +10,7 @@ import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { Provider as ReduxStoreProvider } from 'react-redux';
import '../../../common/mock/match_media';
import {
apolloClientObservable,
mockGlobalState,

View file

@ -10,6 +10,7 @@ import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { Provider as ReduxStoreProvider } from 'react-redux';
import '../../../common/mock/match_media';
import { FlowTargetSourceDest } from '../../../graphql/types';
import {
apolloClientObservable,

View file

@ -10,6 +10,7 @@ import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { Provider as ReduxStoreProvider } from 'react-redux';
import '../../../common/mock/match_media';
import { FlowTargetSourceDest } from '../../../graphql/types';
import {
apolloClientObservable,

View file

@ -7,6 +7,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../../common/mock/match_media';
import { TestProviders } from '../../../common/mock/test_providers';
import { useMountAppended } from '../../../common/utils/use_mount_appended';

View file

@ -11,6 +11,7 @@ import React from 'react';
import { asArrayIfExists } from '../../../common/lib/helpers';
import { getMockNetflowData } from '../../../common/mock';
import '../../../common/mock/match_media';
import { TestProviders } from '../../../common/mock/test_providers';
import { ID_FIELD_NAME } from '../../../common/components/event_details/event_id';
import { useMountAppended } from '../../../common/utils/use_mount_appended';

View file

@ -9,6 +9,7 @@ import React from 'react';
import { asArrayIfExists } from '../../../common/lib/helpers';
import { getMockNetflowData } from '../../../common/mock';
import '../../../common/mock/match_media';
import { TestProviders } from '../../../common/mock/test_providers';
import { ID_FIELD_NAME } from '../../../common/components/event_details/event_id';
import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../ip';

View file

@ -10,6 +10,7 @@ import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { Provider as ReduxStoreProvider } from 'react-redux';
import '../../../common/mock/match_media';
import {
apolloClientObservable,
mockGlobalState,

View file

@ -10,6 +10,7 @@ import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { Provider as ReduxStoreProvider } from 'react-redux';
import '../../../common/mock/match_media';
import { FlowTarget } from '../../../graphql/types';
import {
apolloClientObservable,

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiSpacer } from '@elastic/eui';
import { EuiSpacer, EuiWindowEvent } from '@elastic/eui';
import { noop } from 'lodash/fp';
import React, { useCallback, useMemo } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { useParams } from 'react-router-dom';
@ -23,6 +24,7 @@ import { KpiNetworkComponent } from '..//components/kpi_network';
import { SiemSearchBar } from '../../common/components/search_bar';
import { WrapperPage } from '../../common/components/wrapper_page';
import { KpiNetworkQuery } from '../../network/containers/kpi_network';
import { useFullScreen } from '../../common/containers/use_full_screen';
import { useGlobalTime } from '../../common/containers/use_global_time';
import { useWithSource } from '../../common/containers/source';
import { LastEventIndexKey } from '../../graphql/types';
@ -31,6 +33,7 @@ import { convertToBuildEsQuery } from '../../common/lib/keury';
import { State, inputsSelectors } from '../../common/store';
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../common/store/inputs/actions';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { Display } from '../../hosts/pages/display';
import { networkModel } from '../store';
import { navTabsNetwork, NetworkRoutes, NetworkRoutesLoading } from './navigation';
import { filterNetworkData } from './navigation/alerts_query_tab_body';
@ -52,6 +55,7 @@ const NetworkComponent = React.memo<NetworkComponentProps & PropsFromRedux>(
capabilitiesFetched,
}) => {
const { to, from, setQuery, isInitializing } = useGlobalTime();
const { globalFullScreen } = useFullScreen();
const kibana = useKibana();
const { tabName } = useParams();
@ -95,56 +99,61 @@ const NetworkComponent = React.memo<NetworkComponentProps & PropsFromRedux>(
<>
{indicesExist ? (
<StickyContainer>
<EuiWindowEvent event="resize" handler={noop} />
<FiltersGlobal>
<SiemSearchBar indexPattern={indexPattern} id="global" />
</FiltersGlobal>
<WrapperPage>
<HeaderPage
border
subtitle={<LastEventTime indexKey={LastEventIndexKey.network} />}
title={i18n.PAGE_TITLE}
/>
<WrapperPage noPadding={globalFullScreen}>
<Display show={!globalFullScreen}>
<HeaderPage
border
subtitle={<LastEventTime indexKey={LastEventIndexKey.network} />}
title={i18n.PAGE_TITLE}
/>
<EmbeddedMap
query={query}
filters={filters}
startDate={from}
endDate={to}
setQuery={setQuery}
/>
<EmbeddedMap
query={query}
filters={filters}
startDate={from}
endDate={to}
setQuery={setQuery}
/>
<EuiSpacer />
<EuiSpacer />
<KpiNetworkQuery
endDate={to}
filterQuery={filterQuery}
skip={isInitializing}
sourceId={sourceId}
startDate={from}
>
{({ kpiNetwork, loading, id, inspect, refetch }) => (
<KpiNetworkComponentManage
id={id}
inspect={inspect}
setQuery={setQuery}
refetch={refetch}
data={kpiNetwork}
loading={loading}
from={from}
to={to}
narrowDateRange={narrowDateRange}
/>
)}
</KpiNetworkQuery>
<KpiNetworkQuery
endDate={to}
filterQuery={filterQuery}
skip={isInitializing}
sourceId={sourceId}
startDate={from}
>
{({ kpiNetwork, loading, id, inspect, refetch }) => (
<KpiNetworkComponentManage
id={id}
inspect={inspect}
setQuery={setQuery}
refetch={refetch}
data={kpiNetwork}
loading={loading}
from={from}
to={to}
narrowDateRange={narrowDateRange}
/>
)}
</KpiNetworkQuery>
</Display>
{capabilitiesFetched && !isInitializing ? (
<>
<EuiSpacer />
<Display show={!globalFullScreen}>
<EuiSpacer />
<SiemNavigation navTabs={navTabsNetwork(hasMlUserPermissions)} />
<SiemNavigation navTabs={navTabsNetwork(hasMlUserPermissions)} />
<EuiSpacer />
<EuiSpacer />
</Display>
<NetworkRoutes
filterQuery={tabsFilterQuery}
@ -161,8 +170,6 @@ const NetworkComponent = React.memo<NetworkComponentProps & PropsFromRedux>(
) : (
<NetworkRoutesLoading />
)}
<EuiSpacer />
</WrapperPage>
</StickyContainer>
) : (

View file

@ -11,6 +11,7 @@ import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import { ThemeProvider } from 'styled-components';
import '../../../common/mock/match_media';
import { useQuery } from '../../../common/containers/matrix_histogram';
import { wait } from '../../../common/lib/helpers';
import { mockIndexPattern, TestProviders } from '../../../common/mock';

View file

@ -10,6 +10,7 @@ import React from 'react';
import { OverviewHostProps } from '../overview_host';
import { OverviewNetworkProps } from '../overview_network';
import { mockIndexPattern, TestProviders } from '../../../common/mock';
import '../../../common/mock/match_media';
import { EventCounts } from '.';

View file

@ -6,6 +6,8 @@
import { mount } from 'enzyme';
import React from 'react';
import '../../../../common/mock/match_media';
import { TestProviders } from '../../../../common/mock';
import { EndpointOverview } from './index';

View file

@ -6,6 +6,7 @@
import { shallow } from 'enzyme';
import React from 'react';
import '../../../common/mock/match_media';
import { TestProviders } from '../../../common/mock';
import { HostOverview } from './index';

View file

@ -9,6 +9,7 @@ import { mount } from 'enzyme';
import React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import '../../../common/mock/match_media';
import {
apolloClientObservable,
mockGlobalState,

Some files were not shown because too many files have changed in this diff Show more