[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:
parent
e4f7acb90f
commit
0c0aaf0e6a
|
@ -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-*';
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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__';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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]}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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]}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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',
|
||||
});
|
|
@ -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};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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)`
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -80,6 +80,7 @@ export interface InputsRange {
|
|||
query: Query;
|
||||
filters: Filter[];
|
||||
savedQuery?: SavedQuery;
|
||||
fullScreen?: boolean;
|
||||
}
|
||||
|
||||
export interface LinkTo {
|
||||
|
|
|
@ -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]: {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
) : (
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 '.';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue