Skip to content

Commit

Permalink
[SIEM] Alerts view - adding alerts table (#51959) (#53522)
Browse files Browse the repository at this point in the history
* add alert view to hosts page

* add defaultHeaders

* add alerts table

* fix dsl query

* add alerts histogram

* add i18n for alerts table

* fix types error

* fix type issue

* whitespace cleanup

* fix types

* fix types

* fix types

* fix types

* fix types

* rename params

* fix unit test

* fix types

* revert change on updateHostsSort

* remove unused prop

* update unit test

* pair programming with angela to get filter working

* update alerts query

* clean up

* fix queries

* align type for pageFilters

* apply page filter for network page

* simplify filter props for alerts view

* clean up

* replace hard coded tab name
  • Loading branch information
angorayc authored Dec 18, 2019
1 parent 0f02a54 commit 8fdac0a
Show file tree
Hide file tree
Showing 61 changed files with 1,608 additions and 99 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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 React, { useMemo } from 'react';

import { esFilters } from '../../../../../../../src/plugins/data/common/es_query';
import { StatefulEventsViewer } from '../events_viewer';
import * as i18n from './translations';
import { alertsDefaultModel } from './default_headers';

export interface OwnProps {
end: number;
id: string;
start: number;
}

const ALERTS_TABLE_ID = 'timeline-alerts-table';
const defaultAlertsFilters: esFilters.Filter[] = [
{
meta: {
alias: null,
negate: false,
disabled: false,
type: 'phrase',
key: 'event.kind',
params: {
query: 'alert',
},
},
query: {
bool: {
filter: [
{
bool: {
should: [
{
match: {
'event.kind': 'alert',
},
},
],
minimum_should_match: 1,
},
},
],
},
},
},
];

export const AlertsTable = React.memo(
({
endDate,
startDate,
pageFilters = [],
}: {
endDate: number;
startDate: number;
pageFilters?: esFilters.Filter[];
}) => {
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
return (
<StatefulEventsViewer
defaultFilters={alertsFilter}
defaultModel={alertsDefaultModel}
end={endDate}
id={ALERTS_TABLE_ID}
start={startDate}
timelineTypeContext={useMemo(
() => ({
documentType: i18n.ALERTS_DOCUMENT_TYPE,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
showCheckboxes: false,
showRowRenderers: false,
title: i18n.ALERTS_TABLE_TITLE,
}),
[]
)}
/>
);
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { ColumnHeader } from '../timeline/body/column_headers/column_header';
import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers';
import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../timeline/body/helpers';
import { timelineDefaults, SubsetTimelineModel } from '../../store/timeline/model';

export const alertsHeaders: ColumnHeader[] = [
{
columnHeaderType: defaultColumnHeaderType,
id: '@timestamp',
width: DEFAULT_DATE_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'event.module',
width: DEFAULT_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'event.dataset',
width: DEFAULT_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'event.category',
width: DEFAULT_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'event.severity',
width: DEFAULT_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'observer.name',
width: DEFAULT_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'host.name',
width: DEFAULT_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'message',
width: DEFAULT_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'agent.id',
width: DEFAULT_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'agent.type',
width: DEFAULT_COLUMN_MIN_WIDTH,
},
];

export const alertsDefaultModel: SubsetTimelineModel = {
...timelineDefaults,
columns: alertsHeaders,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { noop } from 'lodash/fp';
import React from 'react';

import { EuiSpacer } from '@elastic/eui';
import { manageQuery } from '../page/manage_query';
import { AlertsOverTimeHistogram } from '../page/hosts/alerts_over_time';
import { AlertsComponentsQueryProps } from './types';
import { AlertsOverTimeQuery } from '../../containers/alerts/alerts_over_time';
import { hostsModel } from '../../store/model';
import { AlertsTable } from './alerts_table';

const AlertsOverTimeManage = manageQuery(AlertsOverTimeHistogram);
export const AlertsView = ({
defaultFilters,
deleteQuery,
endDate,
filterQuery,
pageFilters,
skip,
setQuery,
startDate,
type,
updateDateRange = noop,
}: AlertsComponentsQueryProps) => (
<>
<AlertsOverTimeQuery
endDate={endDate}
filterQuery={filterQuery}
sourceId="default"
startDate={startDate}
type={hostsModel.HostsType.page}
>
{({ alertsOverTime, loading, id, inspect, refetch, totalCount }) => (
<AlertsOverTimeManage
data={alertsOverTime!}
endDate={endDate}
id={id}
inspect={inspect}
loading={loading}
refetch={refetch}
setQuery={setQuery}
startDate={startDate}
totalCount={totalCount}
updateDateRange={updateDateRange}
/>
)}
</AlertsOverTimeQuery>
<EuiSpacer size="l" />
<AlertsTable endDate={endDate} startDate={startDate} pageFilters={pageFilters} />
</>
);

AlertsView.displayName = 'AlertsView';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 ALERTS_DOCUMENT_TYPE = i18n.translate('xpack.siem.hosts.alertsDocumentType', {
defaultMessage: 'Alerts',
});

export const TOTAL_COUNT_OF_ALERTS = i18n.translate('xpack.siem.hosts.totalCountOfAlerts', {
defaultMessage: 'alerts match the search criteria',
});

export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.hosts.alertsDocumentType', {
defaultMessage: 'Alerts',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 { esFilters } from '../../../../../../../src/plugins/data/common';
import { HostsComponentsQueryProps } from '../../pages/hosts/navigation/types';
import { NetworkComponentQueryProps } from '../../pages/network/navigation/types';

type CommonQueryProps = HostsComponentsQueryProps | NetworkComponentQueryProps;
export interface AlertsComponentsQueryProps
extends Pick<
CommonQueryProps,
| 'deleteQuery'
| 'endDate'
| 'filterQuery'
| 'skip'
| 'setQuery'
| 'startDate'
| 'type'
| 'updateDateRange'
> {
pageFilters: esFilters.Filter[];
defaultFilters?: esFilters.Filter[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { isEqual } from 'lodash/fp';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';
import chrome from 'ui/chrome';
Expand All @@ -32,6 +32,7 @@ export interface OwnProps {
id: string;
start: number;
headerFilterGroup?: React.ReactNode;
pageFilters?: esFilters.Filter[];
timelineTypeContext?: TimelineTypeContextProps;
utilityBar?: (totalCount: number) => React.ReactNode;
}
Expand Down Expand Up @@ -150,7 +151,7 @@ const StatefulEventsViewerComponent = React.memo<Props>(

const handleOnMouseEnter = useCallback(() => setShowInspect(true), []);
const handleOnMouseLeave = useCallback(() => setShowInspect(false), []);

const eventsFilter = useMemo(() => [...filters], [defaultFilters]);
return (
<div onMouseEnter={handleOnMouseEnter} onMouseLeave={handleOnMouseLeave}>
<EventsViewer
Expand All @@ -159,7 +160,7 @@ const StatefulEventsViewerComponent = React.memo<Props>(
id={id}
dataProviders={dataProviders!}
end={end}
filters={[...filters, ...defaultFilters]}
filters={eventsFilter}
headerFilterGroup={headerFilterGroup}
indexPattern={indexPatterns ?? { fields: [], title: '' }}
isLive={isLive}
Expand Down Expand Up @@ -192,7 +193,8 @@ const StatefulEventsViewerComponent = React.memo<Props>(
isEqual(prevProps.query, nextProps.query) &&
prevProps.pageCount === nextProps.pageCount &&
isEqual(prevProps.sort, nextProps.sort) &&
prevProps.start === nextProps.start
prevProps.start === nextProps.start &&
isEqual(prevProps.defaultFilters, nextProps.defaultFilters)
);

StatefulEventsViewerComponent.displayName = 'StatefulEventsViewerComponent';
Expand Down Expand Up @@ -227,7 +229,7 @@ export const StatefulEventsViewer = connect(makeMapStateToProps, {
createTimeline: timelineActions.createTimeline,
deleteEventQuery: inputsActions.deleteOneQuery,
updateItemsPerPage: timelineActions.updateItemsPerPage,
updateSort: timelineActions.updateSort,
removeColumn: timelineActions.removeColumn,
upsertColumn: timelineActions.upsertColumn,
setSearchBarFilter: inputsActions.setSearchBarFilter,
})(StatefulEventsViewerComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { encodeIpv6 } from '../../../lib/helpers';

import { getBreadcrumbsForRoute, setBreadcrumbs } from '.';
import { HostsTableType } from '../../../store/hosts/model';
import { RouteSpyState } from '../../../utils/route/types';
import { RouteSpyState, SiemRouteType } from '../../../utils/route/types';
import { TabNavigationProps } from '../tab_navigation/types';
import { NetworkRouteType } from '../../../pages/network/navigation/types';

jest.mock('ui/chrome', () => ({
getBasePath: () => {
Expand All @@ -30,6 +31,17 @@ jest.mock('../../search_bar', () => ({
},
}));

const mockDefaultTab = (pageName: string): SiemRouteType | undefined => {
switch (pageName) {
case 'hosts':
return HostsTableType.authentications;
case 'network':
return NetworkRouteType.flows;
default:
return undefined;
}
};

const getMockObject = (
pageName: string,
pathName: string,
Expand Down Expand Up @@ -69,7 +81,7 @@ const getMockObject = (
pageName,
pathName,
search: '',
tabName: HostsTableType.authentications,
tabName: mockDefaultTab(pageName) as HostsTableType,
query: { query: '', language: 'kuery' },
filters: [],
timeline: {
Expand Down Expand Up @@ -136,6 +148,10 @@ describe('Navigation Breadcrumbs', () => {
href:
'#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{
text: 'Flows',
href: '',
},
]);
});

Expand Down Expand Up @@ -176,7 +192,11 @@ describe('Navigation Breadcrumbs', () => {
href:
'#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{ text: '192.0.2.255', href: '' },
{
text: ipv4,
href: `#/link-to/network/ip/${ipv4}?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
},
{ text: 'Flows', href: '' },
]);
});

Expand All @@ -189,7 +209,11 @@ describe('Navigation Breadcrumbs', () => {
href:
'#/link-to/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))',
},
{ text: '2001:db8:ffff:ffff:ffff:ffff:ffff:ffff', href: '' },
{
text: ipv6,
href: `#/link-to/network/ip/${ipv6Encoded}?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
},
{ text: 'Flows', href: '' },
]);
});
});
Expand Down
Loading

0 comments on commit 8fdac0a

Please sign in to comment.