Skip to content

Commit 5b04df5

Browse files
committed
[SIEM] Detection engine timeline (elastic#53783)
* change create to only have only one form to be open at the same time * add tick to risk score * remove compressed * fix select in schedule * fix bug to not allow more than one step panel to be open at a time * Add a color/health indicator to severity selector * Move and reword tags placeholder to bottom helper text * fix ux on the index patterns field * Reorganize MITRE ATT&CK threat * add url validation + some cleaning to prerp work for UT * add feature to get back timeline + be able to disable action on timeline modal * Add option to import the query from a saved timeline. * wip * Add timeline template selector * fix few bugs from last commit * review I * fix unit test for timeline_title * ui review * fix truncation on timeline selectable
1 parent 70870e2 commit 5b04df5

File tree

84 files changed

+2169
-679
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+2169
-679
lines changed

src/plugins/es_ui_shared/static/forms/components/field.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
RadioGroupField,
3838
RangeField,
3939
SelectField,
40+
SuperSelectField,
4041
ToggleField,
4142
} from './fields';
4243

@@ -50,6 +51,7 @@ const mapTypeToFieldComponent = {
5051
[FIELD_TYPES.RADIO_GROUP]: RadioGroupField,
5152
[FIELD_TYPES.RANGE]: RangeField,
5253
[FIELD_TYPES.SELECT]: SelectField,
54+
[FIELD_TYPES.SUPER_SELECT]: SuperSelectField,
5355
[FIELD_TYPES.TOGGLE]: ToggleField,
5456
};
5557

src/plugins/es_ui_shared/static/forms/components/fields/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ export * from './multi_select_field';
2525
export * from './radio_group_field';
2626
export * from './range_field';
2727
export * from './select_field';
28+
export * from './super_select_field';
2829
export * from './toggle_field';
2930
export * from './text_area_field';
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import React from 'react';
21+
import { EuiFormRow, EuiSuperSelect } from '@elastic/eui';
22+
23+
import { FieldHook, getFieldValidityAndErrorMessage } from '../../hook_form_lib';
24+
25+
interface Props {
26+
field: FieldHook;
27+
euiFieldProps?: Record<string, any>;
28+
idAria?: string;
29+
[key: string]: any;
30+
}
31+
32+
export const SuperSelectField = ({ field, euiFieldProps = {}, ...rest }: Props) => {
33+
const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
34+
35+
return (
36+
<EuiFormRow
37+
label={field.label}
38+
helpText={field.helpText}
39+
error={errorMessage}
40+
isInvalid={isInvalid}
41+
fullWidth
42+
data-test-subj={rest['data-test-subj']}
43+
describedByIds={rest.idAria ? [rest.idAria] : undefined}
44+
>
45+
<EuiSuperSelect
46+
fullWidth
47+
valueOfSelected={field.value as string}
48+
onChange={value => {
49+
field.setValue(value);
50+
}}
51+
options={[]}
52+
isInvalid={isInvalid}
53+
data-test-subj="select"
54+
{...euiFieldProps}
55+
/>
56+
</EuiFormRow>
57+
);
58+
};

src/plugins/es_ui_shared/static/forms/hook_form_lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const FIELD_TYPES = {
2828
RADIO_GROUP: 'radioGroup',
2929
RANGE: 'range',
3030
SELECT: 'select',
31+
SUPER_SELECT: 'superSelect',
3132
MULTI_SELECT: 'multiSelect',
3233
};
3334

x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ export interface QueryTimelineById<TCache> {
181181
apolloClient: ApolloClient<TCache> | ApolloClient<{}> | undefined;
182182
duplicate: boolean;
183183
timelineId: string;
184+
onOpenTimeline?: (timeline: TimelineModel) => void;
184185
openTimeline?: boolean;
185186
updateIsLoading: ActionCreator<{ id: string; isLoading: boolean }>;
186187
updateTimeline: DispatchUpdateTimeline;
@@ -190,6 +191,7 @@ export const queryTimelineById = <TCache>({
190191
apolloClient,
191192
duplicate = false,
192193
timelineId,
194+
onOpenTimeline,
193195
openTimeline = true,
194196
updateIsLoading,
195197
updateTimeline,
@@ -209,7 +211,9 @@ export const queryTimelineById = <TCache>({
209211
);
210212

211213
const { timeline, notes } = formatTimelineResultToModel(timelineToOpen, duplicate);
212-
if (updateTimeline) {
214+
if (onOpenTimeline != null) {
215+
onOpenTimeline(timeline);
216+
} else if (updateTimeline) {
213217
updateTimeline({
214218
duplicate,
215219
from: getOr(getDefaultFromValue(), 'dateRange.start', timeline),

x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,20 @@ import { Dispatch } from 'redux';
1212
import { defaultHeaders } from '../../components/timeline/body/column_headers/default_headers';
1313
import { deleteTimelineMutation } from '../../containers/timeline/delete/persist.gql_query';
1414
import { AllTimelinesVariables, AllTimelinesQuery } from '../../containers/timeline/all';
15-
1615
import { allTimelinesQuery } from '../../containers/timeline/all/index.gql_query';
1716
import { DeleteTimelineMutation, SortFieldTimeline, Direction } from '../../graphql/types';
1817
import { State, timelineSelectors } from '../../store';
18+
import { timelineDefaults, TimelineModel } from '../../store/timeline/model';
1919
import {
2020
createTimeline as dispatchCreateNewTimeline,
2121
updateIsLoading as dispatchUpdateIsLoading,
2222
} from '../../store/timeline/actions';
23+
import { ColumnHeader } from '../timeline/body/column_headers/column_header';
2324
import { OpenTimeline } from './open_timeline';
2425
import { OPEN_TIMELINE_CLASS_NAME, queryTimelineById, dispatchUpdateTimeline } from './helpers';
2526
import { OpenTimelineModalBody } from './open_timeline_modal/open_timeline_modal_body';
2627
import {
28+
ActionTimelineToShow,
2729
DeleteTimelines,
2830
EuiSearchBarQuery,
2931
OnDeleteSelected,
@@ -41,14 +43,14 @@ import {
4143
OpenTimelineReduxProps,
4244
} from './types';
4345
import { DEFAULT_SORT_FIELD, DEFAULT_SORT_DIRECTION } from './constants';
44-
import { ColumnHeader } from '../timeline/body/column_headers/column_header';
45-
import { timelineDefaults } from '../../store/timeline/model';
4646

4747
interface OwnProps<TCache = object> {
4848
apolloClient: ApolloClient<TCache>;
4949
/** Displays open timeline in modal */
5050
isModal: boolean;
5151
closeModalTimeline?: () => void;
52+
hideActions?: ActionTimelineToShow[];
53+
onOpenTimeline?: (timeline: TimelineModel) => void;
5254
}
5355

5456
export type OpenTimelineOwnProps = OwnProps &
@@ -69,15 +71,17 @@ export const getSelectedTimelineIds = (selectedItems: OpenTimelineResult[]): str
6971
/** Manages the state (e.g table selection) of the (pure) `OpenTimeline` component */
7072
export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(
7173
({
74+
apolloClient,
75+
closeModalTimeline,
76+
createNewTimeline,
7277
defaultPageSize,
78+
hideActions = [],
7379
isModal = false,
80+
onOpenTimeline,
81+
timeline,
7482
title,
75-
apolloClient,
76-
closeModalTimeline,
7783
updateTimeline,
7884
updateIsLoading,
79-
timeline,
80-
createNewTimeline,
8185
}) => {
8286
/** Required by EuiTable for expandable rows: a map of `TimelineResult.savedObjectId` to rendered notes */
8387
const [itemIdToExpandedNotesRowMap, setItemIdToExpandedNotesRowMap] = useState<
@@ -212,6 +216,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(
212216
queryTimelineById({
213217
apolloClient,
214218
duplicate,
219+
onOpenTimeline,
215220
timelineId,
216221
updateIsLoading,
217222
updateTimeline,
@@ -286,6 +291,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>(
286291
data-test-subj={'open-timeline-modal'}
287292
deleteTimelines={onDeleteOneTimeline}
288293
defaultPageSize={defaultPageSize}
294+
hideActions={hideActions}
289295
isLoading={loading}
290296
itemIdToExpandedNotesRowMap={itemIdToExpandedNotesRowMap}
291297
onAddTimelinesToFavorites={undefined}

x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ describe('OpenTimeline', () => {
143143
).toBe(true);
144144
});
145145

146-
test('it shows extended columns and actions when onDeleteSelected and deleteTimelines are specified', () => {
146+
test('it shows the delete action columns when onDeleteSelected and deleteTimelines are specified', () => {
147147
const wrapper = mountWithIntl(
148148
<ThemeProvider theme={theme}>
149149
<OpenTimeline
@@ -178,10 +178,10 @@ describe('OpenTimeline', () => {
178178
.first()
179179
.props() as TimelinesTableProps;
180180

181-
expect(props.showExtendedColumnsAndActions).toBe(true);
181+
expect(props.actionTimelineToShow).toContain('delete');
182182
});
183183

184-
test('it does NOT show extended columns and actions when is onDeleteSelected undefined and deleteTimelines is specified', () => {
184+
test('it does NOT show the delete action columns when is onDeleteSelected undefined and deleteTimelines is specified', () => {
185185
const wrapper = mountWithIntl(
186186
<ThemeProvider theme={theme}>
187187
<OpenTimeline
@@ -215,10 +215,10 @@ describe('OpenTimeline', () => {
215215
.first()
216216
.props() as TimelinesTableProps;
217217

218-
expect(props.showExtendedColumnsAndActions).toBe(false);
218+
expect(props.actionTimelineToShow).not.toContain('delete');
219219
});
220220

221-
test('it does NOT show extended columns and actions when is onDeleteSelected provided and deleteTimelines is undefined', () => {
221+
test('it does NOT show the delete action columns when is onDeleteSelected provided and deleteTimelines is undefined', () => {
222222
const wrapper = mountWithIntl(
223223
<ThemeProvider theme={theme}>
224224
<OpenTimeline
@@ -252,10 +252,10 @@ describe('OpenTimeline', () => {
252252
.first()
253253
.props() as TimelinesTableProps;
254254

255-
expect(props.showExtendedColumnsAndActions).toBe(false);
255+
expect(props.actionTimelineToShow).not.toContain('delete');
256256
});
257257

258-
test('it does NOT show extended columns and actions when both onDeleteSelected and deleteTimelines are undefined', () => {
258+
test('it does NOT show the delete action when both onDeleteSelected and deleteTimelines are undefined', () => {
259259
const wrapper = mountWithIntl(
260260
<ThemeProvider theme={theme}>
261261
<OpenTimeline
@@ -288,6 +288,6 @@ describe('OpenTimeline', () => {
288288
.first()
289289
.props() as TimelinesTableProps;
290290

291-
expect(props.showExtendedColumnsAndActions).toBe(false);
291+
expect(props.actionTimelineToShow).not.toContain('delete');
292292
});
293293
});

x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ export const OpenTimeline = React.memo<OpenTimelineProps>(
5757
/>
5858

5959
<TimelinesTable
60+
actionTimelineToShow={
61+
onDeleteSelected != null && deleteTimelines != null
62+
? ['delete', 'duplicate', 'selectable']
63+
: ['duplicate', 'selectable']
64+
}
6065
data-test-subj="timelines-table"
6166
deleteTimelines={deleteTimelines}
6267
defaultPageSize={defaultPageSize}
@@ -69,7 +74,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>(
6974
pageIndex={pageIndex}
7075
pageSize={pageSize}
7176
searchResults={searchResults}
72-
showExtendedColumnsAndActions={onDeleteSelected != null && deleteTimelines != null}
77+
showExtendedColumns={true}
7378
sortDirection={sortDirection}
7479
sortField={sortField}
7580
totalSearchResultsCount={totalSearchResultsCount}

x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,49 @@
77
import { EuiModal, EuiOverlayMask } from '@elastic/eui';
88
import React from 'react';
99

10+
import { TimelineModel } from '../../../store/timeline/model';
1011
import { useApolloClient } from '../../../utils/apollo_context';
12+
1113
import * as i18n from '../translations';
14+
import { ActionTimelineToShow } from '../types';
1215
import { StatefulOpenTimeline } from '..';
1316

1417
export interface OpenTimelineModalProps {
1518
onClose: () => void;
19+
hideActions?: ActionTimelineToShow[];
20+
modalTitle?: string;
21+
onOpen?: (timeline: TimelineModel) => void;
1622
}
1723

1824
const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
1925
const OPEN_TIMELINE_MODAL_WIDTH = 1000; // px
2026

21-
export const OpenTimelineModal = React.memo<OpenTimelineModalProps>(({ onClose }) => {
22-
const apolloClient = useApolloClient();
23-
24-
if (!apolloClient) return null;
25-
26-
return (
27-
<EuiOverlayMask>
28-
<EuiModal
29-
data-test-subj="open-timeline-modal"
30-
maxWidth={OPEN_TIMELINE_MODAL_WIDTH}
31-
onClose={onClose}
32-
>
33-
<StatefulOpenTimeline
34-
apolloClient={apolloClient}
35-
closeModalTimeline={onClose}
36-
isModal={true}
37-
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
38-
title={i18n.OPEN_TIMELINE_TITLE}
39-
/>
40-
</EuiModal>
41-
</EuiOverlayMask>
42-
);
43-
});
27+
export const OpenTimelineModal = React.memo<OpenTimelineModalProps>(
28+
({ hideActions = [], modalTitle, onClose, onOpen }) => {
29+
const apolloClient = useApolloClient();
30+
31+
if (!apolloClient) return null;
32+
33+
return (
34+
<EuiOverlayMask>
35+
<EuiModal
36+
data-test-subj="open-timeline-modal"
37+
maxWidth={OPEN_TIMELINE_MODAL_WIDTH}
38+
onClose={onClose}
39+
>
40+
<StatefulOpenTimeline
41+
apolloClient={apolloClient}
42+
closeModalTimeline={onClose}
43+
hideActions={hideActions}
44+
isModal={true}
45+
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
46+
onOpenTimeline={onOpen}
47+
title={modalTitle ?? i18n.OPEN_TIMELINE_TITLE}
48+
/>
49+
</EuiModal>
50+
</EuiOverlayMask>
51+
);
52+
}
53+
);
4454

4555
OpenTimelineModal.displayName = 'OpenTimelineModal';

x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ describe('OpenTimelineModal', () => {
143143
).toBe(true);
144144
});
145145

146-
test('it shows extended columns and actions when onDeleteSelected and deleteTimelines are specified', () => {
146+
test('it shows the delete action when onDeleteSelected and deleteTimelines are specified', () => {
147147
const wrapper = mountWithIntl(
148148
<ThemeProvider theme={theme}>
149149
<OpenTimelineModalBody
@@ -178,10 +178,10 @@ describe('OpenTimelineModal', () => {
178178
.first()
179179
.props() as TimelinesTableProps;
180180

181-
expect(props.showExtendedColumnsAndActions).toBe(true);
181+
expect(props.actionTimelineToShow).toContain('delete');
182182
});
183183

184-
test('it does NOT show extended columns and actions when is onDeleteSelected undefined and deleteTimelines is specified', () => {
184+
test('it does NOT show the delete when is onDeleteSelected undefined and deleteTimelines is specified', () => {
185185
const wrapper = mountWithIntl(
186186
<ThemeProvider theme={theme}>
187187
<OpenTimelineModalBody
@@ -215,10 +215,10 @@ describe('OpenTimelineModal', () => {
215215
.first()
216216
.props() as TimelinesTableProps;
217217

218-
expect(props.showExtendedColumnsAndActions).toBe(false);
218+
expect(props.actionTimelineToShow).not.toContain('delete');
219219
});
220220

221-
test('it does NOT show extended columns and actions when is onDeleteSelected provided and deleteTimelines is undefined', () => {
221+
test('it does NOT show the delete action when is onDeleteSelected provided and deleteTimelines is undefined', () => {
222222
const wrapper = mountWithIntl(
223223
<ThemeProvider theme={theme}>
224224
<OpenTimelineModalBody
@@ -252,10 +252,10 @@ describe('OpenTimelineModal', () => {
252252
.first()
253253
.props() as TimelinesTableProps;
254254

255-
expect(props.showExtendedColumnsAndActions).toBe(false);
255+
expect(props.actionTimelineToShow).not.toContain('delete');
256256
});
257257

258-
test('it does NOT show extended columns and actions when both onDeleteSelected and deleteTimelines are undefined', () => {
258+
test('it does NOT show extended columns when both onDeleteSelected and deleteTimelines are undefined', () => {
259259
const wrapper = mountWithIntl(
260260
<ThemeProvider theme={theme}>
261261
<OpenTimelineModalBody
@@ -288,6 +288,6 @@ describe('OpenTimelineModal', () => {
288288
.first()
289289
.props() as TimelinesTableProps;
290290

291-
expect(props.showExtendedColumnsAndActions).toBe(false);
291+
expect(props.actionTimelineToShow).not.toContain('delete');
292292
});
293293
});

0 commit comments

Comments
 (0)