diff --git a/x-pack/plugins/siem/common/constants.ts b/x-pack/plugins/siem/common/constants.ts
index a80f7f04fbfa0..c14e5d1066518 100644
--- a/x-pack/plugins/siem/common/constants.ts
+++ b/x-pack/plugins/siem/common/constants.ts
@@ -30,6 +30,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 = [
diff --git a/x-pack/plugins/siem/public/components/inspect/modal.test.tsx b/x-pack/plugins/siem/public/components/inspect/modal.test.tsx
index 67cc2624a6aba..a2cba8335ba9d 100644
--- a/x-pack/plugins/siem/public/components/inspect/modal.test.tsx
+++ b/x-pack/plugins/siem/public/components/inspect/modal.test.tsx
@@ -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({'No alert index found'});
+ });
+
+ 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.');
+ });
+ });
});
diff --git a/x-pack/plugins/siem/public/components/inspect/modal.tsx b/x-pack/plugins/siem/public/components/inspect/modal.tsx
index 1563c005af5b6..2e78df31555d7 100644
--- a/x-pack/plugins/siem/public/components/inspect/modal.tsx
+++ b/x-pack/plugins/siem/public/components/inspect/modal.tsx
@@ -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 | Response): string =>
}
};
+export const formatIndexPatternRequested = (indices: string[] = []) => {
+ if (indices.length === 1 && indices[0] === NO_ALERT_INDEX) {
+ return {i18n.NO_ALERT_INDEX_FOUND};
+ }
+ 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: (
- {inspectRequest != null ? inspectRequest.index.join(', ') : i18n.SOMETHING_WENT_WRONG}
+ {formatIndexPatternRequested(inspectRequest?.index ?? [])}
),
},
diff --git a/x-pack/plugins/siem/public/components/inspect/translations.ts b/x-pack/plugins/siem/public/components/inspect/translations.ts
index 43248773787ab..bfa6bd7609fd0 100644
--- a/x-pack/plugins/siem/public/components/inspect/translations.ts
+++ b/x-pack/plugins/siem/public/components/inspect/translations.ts
@@ -51,3 +51,7 @@ 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.siem.inspect.modal.noAlertIndexFound', {
+ defaultMessage: 'No alert index found',
+});
diff --git a/x-pack/plugins/siem/public/components/timeline/index.test.tsx b/x-pack/plugins/siem/public/components/timeline/index.test.tsx
new file mode 100644
index 0000000000000..73ea2649b498f
--- /dev/null
+++ b/x-pack/plugins/siem/public/components/timeline/index.test.tsx
@@ -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 '../../containers/detection_engine/signals/use_signal_index';
+import { mocksSource } from '../../containers/source/mock';
+import { wait } from '../../lib/helpers';
+import { defaultHeaders, mockTimelineData, TestProviders } from '../../mock';
+import { Direction } from '../../graphql/types';
+import { timelineQuery } from '../../containers/timeline/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('../../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;
+jest.mock('../../containers/detection_engine/signals/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(
+
+
+
+
+
+ );
+ 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(
+
+
+
+
+
+ );
+ await act(async () => {
+ await wait();
+ wrapper.update();
+ const timeline = wrapper.find(Timeline);
+ expect(timeline.props().indexToAdd).toEqual(['mock-siem-signals-index']);
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/siem/public/components/timeline/index.tsx b/x-pack/plugins/siem/public/components/timeline/index.tsx
index ad985fe63fd8f..fb23252461762 100644
--- a/x-pack/plugins/siem/public/components/timeline/index.tsx
+++ b/x-pack/plugins/siem/public/components/timeline/index.tsx
@@ -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 '../../containers/source';
import { useSignalIndex } from '../../containers/detection_engine/signals/use_signal_index';
import { inputsModel, inputsSelectors, State, timelineSelectors } from '../../store';
@@ -30,7 +31,7 @@ export interface OwnProps {
usersViewing: string[];
}
-type Props = OwnProps & PropsFromRedux;
+export type Props = OwnProps & PropsFromRedux;
const StatefulTimelineComponent = React.memo(
({
@@ -71,7 +72,7 @@ const StatefulTimelineComponent = React.memo(
) {
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(