[SECURITY SOLEIL] Fix selection of event type when no siem index signal created (#68291)
* fix selection of event type when no siem index signal created * including the term signal for the old timeline * fix import path * Add a specific msg in the inspect modal if we do not have the alert index created * fix import if eventType is siganl to match it to alert * forget to update test * fix signal view Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
eaca7ee008
commit
e3d88a4f09
9 changed files with 188 additions and 8 deletions
|
@ -31,6 +31,7 @@ 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 NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51';
|
||||
|
||||
/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */
|
||||
export const DEFAULT_INDEX_PATTERN = [
|
||||
|
|
|
@ -9,7 +9,8 @@ import { mount } from 'enzyme';
|
|||
import React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { ModalInspectQuery } from './modal';
|
||||
import { NO_ALERT_INDEX } from '../../../../common/constants';
|
||||
import { ModalInspectQuery, formatIndexPatternRequested } from './modal';
|
||||
|
||||
const request =
|
||||
'{"index": ["auditbeat-*","filebeat-*","packetbeat-*","winlogbeat-*"],"allowNoIndices": true, "ignoreUnavailable": true, "body": { "aggregations": {"hosts": {"cardinality": {"field": "host.name" } }, "hosts_histogram": {"auto_date_histogram": {"field": "@timestamp","buckets": "6"},"aggs": { "count": {"cardinality": {"field": "host.name" }}}}}, "query": {"bool": {"filter": [{"range": { "@timestamp": {"gte": 1562290224506,"lte": 1562376624506 }}}]}}, "size": 0, "track_total_hits": false}}';
|
||||
|
@ -244,4 +245,31 @@ describe('Modal Inspect', () => {
|
|||
expect(closeModal).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatIndexPatternRequested', () => {
|
||||
test('Return specific messages to NO_ALERT_INDEX if we only have one index and we match the index name `NO_ALERT_INDEX`', () => {
|
||||
const expected = formatIndexPatternRequested([NO_ALERT_INDEX]);
|
||||
expect(expected).toEqual(<i>{'No alert index found'}</i>);
|
||||
});
|
||||
|
||||
test('Ignore NO_ALERT_INDEX if you have more than one indices', () => {
|
||||
const expected = formatIndexPatternRequested([NO_ALERT_INDEX, 'indice-1']);
|
||||
expect(expected).toEqual('indice-1');
|
||||
});
|
||||
|
||||
test('Happy path', () => {
|
||||
const expected = formatIndexPatternRequested(['indice-1, indice-2']);
|
||||
expect(expected).toEqual('indice-1, indice-2');
|
||||
});
|
||||
|
||||
test('Empty array with no indices', () => {
|
||||
const expected = formatIndexPatternRequested([]);
|
||||
expect(expected).toEqual('Sorry about that, something went wrong.');
|
||||
});
|
||||
|
||||
test('Undefined indices', () => {
|
||||
const expected = formatIndexPatternRequested(undefined);
|
||||
expect(expected).toEqual('Sorry about that, something went wrong.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@ import numeral from '@elastic/numeral';
|
|||
import React, { ReactNode } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { NO_ALERT_INDEX } from '../../../../common/constants';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const DescriptionListStyled = styled(EuiDescriptionList)`
|
||||
|
@ -88,6 +89,15 @@ const manageStringify = (object: Record<string, unknown> | Response): string =>
|
|||
}
|
||||
};
|
||||
|
||||
export const formatIndexPatternRequested = (indices: string[] = []) => {
|
||||
if (indices.length === 1 && indices[0] === NO_ALERT_INDEX) {
|
||||
return <i>{i18n.NO_ALERT_INDEX_FOUND}</i>;
|
||||
}
|
||||
return indices.length > 0
|
||||
? indices.filter((i) => i !== NO_ALERT_INDEX).join(', ')
|
||||
: i18n.SOMETHING_WENT_WRONG;
|
||||
};
|
||||
|
||||
export const ModalInspectQuery = ({
|
||||
closeModal,
|
||||
isShowing = false,
|
||||
|
@ -113,7 +123,7 @@ export const ModalInspectQuery = ({
|
|||
),
|
||||
description: (
|
||||
<span data-test-subj="index-pattern-description">
|
||||
{inspectRequest != null ? inspectRequest.index.join(', ') : i18n.SOMETHING_WENT_WRONG}
|
||||
{formatIndexPatternRequested(inspectRequest?.index ?? [])}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -60,3 +60,10 @@ export const REQUEST_TIMESTAMP_DESC = i18n.translate(
|
|||
defaultMessage: 'Time when the start of the request has been logged',
|
||||
}
|
||||
);
|
||||
|
||||
export const NO_ALERT_INDEX_FOUND = i18n.translate(
|
||||
'xpack.securitySolution.inspect.modal.noAlertIndexFound',
|
||||
{
|
||||
defaultMessage: 'No alert index found',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { MockedProvider } from 'react-apollo/test-utils';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import useResizeObserver from 'use-resize-observer/polyfilled';
|
||||
|
||||
import {
|
||||
useSignalIndex,
|
||||
ReturnSignalIndex,
|
||||
} from '../../../alerts/containers/detection_engine/alerts/use_signal_index';
|
||||
import { mocksSource } from '../../../common/containers/source/mock';
|
||||
import { wait } from '../../../common/lib/helpers';
|
||||
import { defaultHeaders, mockTimelineData, TestProviders } from '../../../common/mock';
|
||||
import { Direction } from '../../../graphql/types';
|
||||
import { timelineQuery } from '../../containers/index.gql_query';
|
||||
import { timelineActions } from '../../store/timeline';
|
||||
|
||||
import { Sort } from './body/sort';
|
||||
import { mockDataProviders } from './data_providers/mock/mock_data_providers';
|
||||
import { StatefulTimeline, Props as StatefulTimelineProps } from './index';
|
||||
import { Timeline } from './timeline';
|
||||
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
|
||||
jest.mock('use-resize-observer/polyfilled');
|
||||
mockUseResizeObserver.mockImplementation(() => ({}));
|
||||
|
||||
const mockUseSignalIndex: jest.Mock = useSignalIndex as jest.Mock<ReturnSignalIndex>;
|
||||
jest.mock('../../../alerts/containers/detection_engine/alerts/use_signal_index');
|
||||
|
||||
describe('StatefulTimeline', () => {
|
||||
let props = {} as StatefulTimelineProps;
|
||||
const sort: Sort = {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: Direction.desc,
|
||||
};
|
||||
const startDate = new Date('2018-03-23T18:49:23.132Z').valueOf();
|
||||
const endDate = new Date('2018-03-24T03:33:52.253Z').valueOf();
|
||||
|
||||
const mocks = [
|
||||
{ request: { query: timelineQuery }, result: { data: { events: mockTimelineData } } },
|
||||
...mocksSource,
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
addProvider: timelineActions.addProvider,
|
||||
columns: defaultHeaders,
|
||||
createTimeline: timelineActions.createTimeline,
|
||||
dataProviders: mockDataProviders,
|
||||
eventType: 'raw',
|
||||
end: endDate,
|
||||
filters: [],
|
||||
id: 'foo',
|
||||
isLive: false,
|
||||
itemsPerPage: 5,
|
||||
itemsPerPageOptions: [5, 10, 20],
|
||||
kqlMode: 'search',
|
||||
kqlQueryExpression: '',
|
||||
onClose: jest.fn(),
|
||||
onDataProviderEdited: timelineActions.dataProviderEdited,
|
||||
removeColumn: timelineActions.removeColumn,
|
||||
removeProvider: timelineActions.removeProvider,
|
||||
show: true,
|
||||
showCallOutUnauthorizedMsg: false,
|
||||
sort,
|
||||
start: startDate,
|
||||
updateColumns: timelineActions.updateColumns,
|
||||
updateDataProviderEnabled: timelineActions.updateDataProviderEnabled,
|
||||
updateDataProviderExcluded: timelineActions.updateDataProviderExcluded,
|
||||
updateDataProviderKqlQuery: timelineActions.updateDataProviderKqlQuery,
|
||||
updateHighlightedDropAndProviderId: timelineActions.updateHighlightedDropAndProviderId,
|
||||
updateItemsPerPage: timelineActions.updateItemsPerPage,
|
||||
updateItemsPerPageOptions: timelineActions.updateItemsPerPageOptions,
|
||||
updateSort: timelineActions.updateSort,
|
||||
upsertColumn: timelineActions.upsertColumn,
|
||||
usersViewing: ['elastic'],
|
||||
};
|
||||
});
|
||||
|
||||
describe('indexToAdd', () => {
|
||||
test('Make sure that indexToAdd return an unknown index if signalIndex does not exist', async () => {
|
||||
mockUseSignalIndex.mockImplementation(() => ({
|
||||
loading: false,
|
||||
signalIndexExists: false,
|
||||
signalIndexName: undefined,
|
||||
}));
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<StatefulTimeline {...props} />
|
||||
</MockedProvider>
|
||||
</TestProviders>
|
||||
);
|
||||
await act(async () => {
|
||||
await wait();
|
||||
wrapper.update();
|
||||
const timeline = wrapper.find(Timeline);
|
||||
expect(timeline.props().indexToAdd).toEqual([
|
||||
'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('Make sure that indexToAdd return siem signal index if signalIndex exist', async () => {
|
||||
mockUseSignalIndex.mockImplementation(() => ({
|
||||
loading: false,
|
||||
signalIndexExists: true,
|
||||
signalIndexName: 'mock-siem-signals-index',
|
||||
}));
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<StatefulTimeline {...props} />
|
||||
</MockedProvider>
|
||||
</TestProviders>
|
||||
);
|
||||
await act(async () => {
|
||||
await wait();
|
||||
wrapper.update();
|
||||
const timeline = wrapper.find(Timeline);
|
||||
expect(timeline.props().indexToAdd).toEqual(['mock-siem-signals-index']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,6 +8,7 @@ import React, { useEffect, useCallback, useMemo } from 'react';
|
|||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { NO_ALERT_INDEX } from '../../../../common/constants';
|
||||
import { WithSource } from '../../../common/containers/source';
|
||||
import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index';
|
||||
import { inputsModel, inputsSelectors, State } from '../../../common/store';
|
||||
|
@ -30,7 +31,7 @@ export interface OwnProps {
|
|||
usersViewing: string[];
|
||||
}
|
||||
|
||||
type Props = OwnProps & PropsFromRedux;
|
||||
export type Props = OwnProps & PropsFromRedux;
|
||||
|
||||
const StatefulTimelineComponent = React.memo<Props>(
|
||||
({
|
||||
|
@ -67,11 +68,11 @@ const StatefulTimelineComponent = React.memo<Props>(
|
|||
eventType &&
|
||||
signalIndexExists &&
|
||||
signalIndexName != null &&
|
||||
['signal', 'all'].includes(eventType)
|
||||
['signal', 'alert', 'all'].includes(eventType)
|
||||
) {
|
||||
return [signalIndexName];
|
||||
}
|
||||
return [];
|
||||
return [NO_ALERT_INDEX]; // Following index does not exist so we won't show any events;
|
||||
}, [eventType, signalIndexExists, signalIndexName]);
|
||||
|
||||
const onDataProviderRemoved: OnDataProviderRemoved = useCallback(
|
||||
|
|
|
@ -79,7 +79,7 @@ const PickEventTypeComponents: React.FC<PickEventTypeProps> = ({
|
|||
<EuiSuperSelect
|
||||
data-test-subj="pick-event-type"
|
||||
fullWidth={false}
|
||||
valueOfSelected={eventType}
|
||||
valueOfSelected={eventType === 'signal' ? 'alert' : eventType}
|
||||
onChange={onChangeEventType}
|
||||
options={eventTypeOptions}
|
||||
/>
|
||||
|
|
|
@ -92,7 +92,7 @@ class TimelineQueryComponent extends QueryTemplate<
|
|||
indexPattern == null || (indexPattern != null && indexPattern.title === '')
|
||||
? [
|
||||
...(['all', 'raw'].includes(eventType) ? defaultKibanaIndex : []),
|
||||
...(['all', 'signal'].includes(eventType) ? indexToAdd : []),
|
||||
...(['all', 'alert', 'signal'].includes(eventType) ? indexToAdd : []),
|
||||
]
|
||||
: indexPattern?.title.split(',') ?? [];
|
||||
const variables: GetTimelineQuery.Variables = {
|
||||
|
|
|
@ -18,7 +18,7 @@ import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/t
|
|||
|
||||
export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages
|
||||
export type KqlMode = 'filter' | 'search';
|
||||
export type EventType = 'all' | 'raw' | 'alert';
|
||||
export type EventType = 'all' | 'raw' | 'alert' | 'signal';
|
||||
|
||||
export type ColumnHeaderType = 'not-filtered' | 'text-filter';
|
||||
|
||||
|
|
Loading…
Reference in a new issue