Skip to content

Commit 5726ca7

Browse files
[Security Solution][Timeline] Fix timeline styling and createFrom beh… (#72152)
1 parent 2a1ce2b commit 5726ca7

File tree

16 files changed

+297
-69
lines changed

16 files changed

+297
-69
lines changed

x-pack/plugins/security_solution/common/constants.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,3 @@ export const showAllOthersBucket: string[] = [
167167
'destination.ip',
168168
'user.name',
169169
];
170-
171-
/*
172-
* This should be set to true after https://github.com/elastic/kibana/pull/67496 is merged
173-
*/
174-
export const enableElasticFilter = false;

x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export const reformatDataProviderWithNewValue = <T extends DataProvider | DataPr
148148
timelineType: TimelineType = TimelineType.default
149149
): T => {
150150
// Support for legacy "template-like" timeline behavior that is using hardcoded list of templateFields
151-
if (timelineType === TimelineType.default) {
151+
if (timelineType !== TimelineType.template) {
152152
if (templateFields.includes(dataProvider.queryMatch.field)) {
153153
const newValue = getStringArray(dataProvider.queryMatch.field, ecsData);
154154
if (newValue.length) {

x-pack/plugins/security_solution/public/graphql/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5654,6 +5654,8 @@ export namespace GetOneTimeline {
56545654

56555655
kqlQuery: Maybe<string>;
56565656

5657+
type: Maybe<DataProviderType>;
5658+
56575659
queryMatch: Maybe<_QueryMatch>;
56585660
};
56595661

@@ -5870,6 +5872,8 @@ export namespace PersistTimelineMutation {
58705872

58715873
eventType: Maybe<string>;
58725874

5875+
excludedRowRendererIds: Maybe<RowRendererId[]>;
5876+
58735877
favorite: Maybe<Favorite[]>;
58745878

58755879
filters: Maybe<Filters[]>;
@@ -5932,6 +5936,8 @@ export namespace PersistTimelineMutation {
59325936

59335937
kqlQuery: Maybe<string>;
59345938

5939+
type: Maybe<DataProviderType>;
5940+
59355941
queryMatch: Maybe<QueryMatch>;
59365942

59375943
and: Maybe<And[]>;
@@ -5964,6 +5970,8 @@ export namespace PersistTimelineMutation {
59645970

59655971
kqlQuery: Maybe<string>;
59665972

5973+
type: Maybe<DataProviderType>;
5974+
59675975
queryMatch: Maybe<_QueryMatch>;
59685976
};
59695977

x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,203 @@ describe('helpers', () => {
306306
width: 1100,
307307
});
308308
});
309+
310+
test('if duplicates and timeline.timelineType is not matching with outcome timelineType it should return draft with empty title', () => {
311+
const timeline = {
312+
savedObjectId: 'savedObject-1',
313+
title: 'Awesome Timeline',
314+
version: '1',
315+
status: TimelineStatus.active,
316+
timelineType: TimelineType.default,
317+
};
318+
319+
const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.template);
320+
expect(newTimeline).toEqual({
321+
columns: [
322+
{
323+
columnHeaderType: 'not-filtered',
324+
id: '@timestamp',
325+
width: 190,
326+
},
327+
{
328+
columnHeaderType: 'not-filtered',
329+
id: 'message',
330+
width: 180,
331+
},
332+
{
333+
columnHeaderType: 'not-filtered',
334+
id: 'event.category',
335+
width: 180,
336+
},
337+
{
338+
columnHeaderType: 'not-filtered',
339+
id: 'event.action',
340+
width: 180,
341+
},
342+
{
343+
columnHeaderType: 'not-filtered',
344+
id: 'host.name',
345+
width: 180,
346+
},
347+
{
348+
columnHeaderType: 'not-filtered',
349+
id: 'source.ip',
350+
width: 180,
351+
},
352+
{
353+
columnHeaderType: 'not-filtered',
354+
id: 'destination.ip',
355+
width: 180,
356+
},
357+
{
358+
columnHeaderType: 'not-filtered',
359+
id: 'user.name',
360+
width: 180,
361+
},
362+
],
363+
dataProviders: [],
364+
dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' },
365+
description: '',
366+
deletedEventIds: [],
367+
eventIdToNoteIds: {},
368+
eventType: 'all',
369+
excludedRowRendererIds: [],
370+
filters: [],
371+
highlightedDropAndProviderId: '',
372+
historyIds: [],
373+
id: 'savedObject-1',
374+
isFavorite: false,
375+
isLive: false,
376+
isSelectAllChecked: false,
377+
isLoading: false,
378+
isSaving: false,
379+
itemsPerPage: 25,
380+
itemsPerPageOptions: [10, 25, 50, 100],
381+
kqlMode: 'filter',
382+
kqlQuery: {
383+
filterQuery: null,
384+
filterQueryDraft: null,
385+
},
386+
loadingEventIds: [],
387+
noteIds: [],
388+
pinnedEventIds: {},
389+
pinnedEventsSaveObject: {},
390+
savedObjectId: 'savedObject-1',
391+
selectedEventIds: {},
392+
show: false,
393+
showCheckboxes: false,
394+
sort: {
395+
columnId: '@timestamp',
396+
sortDirection: 'desc',
397+
},
398+
status: TimelineStatus.draft,
399+
title: '',
400+
timelineType: TimelineType.template,
401+
templateTimelineId: null,
402+
templateTimelineVersion: null,
403+
version: '1',
404+
width: 1100,
405+
});
406+
});
407+
408+
test('if duplicates and timeline.timelineType is not matching with outcome timelineType it should return draft with empty title template', () => {
409+
const timeline = {
410+
savedObjectId: 'savedObject-1',
411+
title: 'Awesome Template',
412+
version: '1',
413+
status: TimelineStatus.active,
414+
timelineType: TimelineType.template,
415+
};
416+
417+
const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.default);
418+
expect(newTimeline).toEqual({
419+
columns: [
420+
{
421+
columnHeaderType: 'not-filtered',
422+
id: '@timestamp',
423+
width: 190,
424+
},
425+
{
426+
columnHeaderType: 'not-filtered',
427+
id: 'message',
428+
width: 180,
429+
},
430+
{
431+
columnHeaderType: 'not-filtered',
432+
id: 'event.category',
433+
width: 180,
434+
},
435+
{
436+
columnHeaderType: 'not-filtered',
437+
id: 'event.action',
438+
width: 180,
439+
},
440+
{
441+
columnHeaderType: 'not-filtered',
442+
id: 'host.name',
443+
width: 180,
444+
},
445+
{
446+
columnHeaderType: 'not-filtered',
447+
id: 'source.ip',
448+
width: 180,
449+
},
450+
{
451+
columnHeaderType: 'not-filtered',
452+
id: 'destination.ip',
453+
width: 180,
454+
},
455+
{
456+
columnHeaderType: 'not-filtered',
457+
id: 'user.name',
458+
width: 180,
459+
},
460+
],
461+
dataProviders: [],
462+
dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' },
463+
description: '',
464+
deletedEventIds: [],
465+
eventIdToNoteIds: {},
466+
eventType: 'all',
467+
excludedRowRendererIds: [],
468+
filters: [],
469+
highlightedDropAndProviderId: '',
470+
historyIds: [],
471+
id: 'savedObject-1',
472+
isFavorite: false,
473+
isLive: false,
474+
isSelectAllChecked: false,
475+
isLoading: false,
476+
isSaving: false,
477+
itemsPerPage: 25,
478+
itemsPerPageOptions: [10, 25, 50, 100],
479+
kqlMode: 'filter',
480+
kqlQuery: {
481+
filterQuery: null,
482+
filterQueryDraft: null,
483+
},
484+
loadingEventIds: [],
485+
noteIds: [],
486+
pinnedEventIds: {},
487+
pinnedEventsSaveObject: {},
488+
savedObjectId: 'savedObject-1',
489+
selectedEventIds: {},
490+
show: false,
491+
showCheckboxes: false,
492+
sort: {
493+
columnId: '@timestamp',
494+
sortDirection: 'desc',
495+
},
496+
status: TimelineStatus.draft,
497+
title: '',
498+
timelineType: TimelineType.default,
499+
templateTimelineId: null,
500+
templateTimelineVersion: null,
501+
version: '1',
502+
width: 1100,
503+
});
504+
});
505+
309506
test('if columns are null, we should get the default columns', () => {
310507
const timeline = {
311508
savedObjectId: 'savedObject-1',

x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -173,29 +173,33 @@ const getTemplateTimelineId = (
173173
duplicate: boolean,
174174
targetTimelineType?: TimelineType
175175
) => {
176-
if (!duplicate) {
177-
return timeline.templateTimelineId;
178-
}
179-
180176
if (
181177
targetTimelineType === TimelineType.default &&
182178
timeline.timelineType === TimelineType.template
183179
) {
184180
return timeline.templateTimelineId;
185181
}
186182

187-
// TODO: MOVE TO BACKEND
188-
return uuid.v4();
183+
return duplicate && timeline.timelineType === TimelineType.template
184+
? // TODO: MOVE TO THE BACKEND
185+
uuid.v4()
186+
: timeline.templateTimelineId;
189187
};
190188

191-
const convertToDefaultField = ({ and, ...dataProvider }: DataProviderResult) =>
192-
deepMerge(dataProvider, {
193-
type: DataProviderType.default,
194-
queryMatch: {
195-
value:
196-
dataProvider.queryMatch!.operator === IS_OPERATOR ? '' : dataProvider.queryMatch!.value,
197-
},
198-
});
189+
const convertToDefaultField = ({ and, ...dataProvider }: DataProviderResult) => {
190+
if (dataProvider.type === DataProviderType.template) {
191+
return deepMerge(dataProvider, {
192+
type: DataProviderType.default,
193+
enabled: dataProvider.queryMatch!.operator !== IS_OPERATOR,
194+
queryMatch: {
195+
value:
196+
dataProvider.queryMatch!.operator === IS_OPERATOR ? '' : dataProvider.queryMatch!.value,
197+
},
198+
});
199+
}
200+
201+
return dataProvider;
202+
};
199203

200204
const getDataProviders = (
201205
duplicate: boolean,
@@ -212,6 +216,28 @@ const getDataProviders = (
212216
return dataProviders;
213217
};
214218

219+
export const getTimelineTitle = (
220+
timeline: TimelineResult,
221+
duplicate: boolean,
222+
timelineType?: TimelineType
223+
) => {
224+
const isCreateTimelineFromAction = timelineType && timeline.timelineType !== timelineType;
225+
if (isCreateTimelineFromAction) return '';
226+
227+
return duplicate ? `${timeline.title} - Duplicate` : timeline.title || '';
228+
};
229+
230+
export const getTimelineStatus = (
231+
timeline: TimelineResult,
232+
duplicate: boolean,
233+
timelineType?: TimelineType
234+
) => {
235+
const isCreateTimelineFromAction = timelineType && timeline.timelineType !== timelineType;
236+
if (isCreateTimelineFromAction) return TimelineStatus.draft;
237+
238+
return duplicate ? TimelineStatus.active : timeline.status;
239+
};
240+
215241
// eslint-disable-next-line complexity
216242
export const defaultTimelineToTimelineModel = (
217243
timeline: TimelineResult,
@@ -234,11 +260,11 @@ export const defaultTimelineToTimelineModel = (
234260
pinnedEventIds: setPinnedEventIds(duplicate, timeline.pinnedEventIds),
235261
pinnedEventsSaveObject: setPinnedEventsSaveObject(duplicate, timeline.pinnedEventsSaveObject),
236262
id: duplicate ? '' : timeline.savedObjectId,
237-
status: duplicate ? TimelineStatus.active : timeline.status,
263+
status: getTimelineStatus(timeline, duplicate, timelineType),
238264
savedObjectId: duplicate ? null : timeline.savedObjectId,
239265
version: duplicate ? null : timeline.version,
240266
timelineType: timelineType ?? timeline.timelineType,
241-
title: duplicate ? `${timeline.title} - Duplicate` : timeline.title || '',
267+
title: getTimelineTitle(timeline, duplicate, timelineType),
242268
templateTimelineId: getTemplateTimelineId(timeline, duplicate, timelineType),
243269
templateTimelineVersion: duplicate && isTemplate ? 1 : timeline.templateTimelineVersion,
244270
};

x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { EuiPanel, EuiBasicTable, EuiSpacer } from '@elastic/eui';
7+
import { EuiPanel, EuiBasicTable } from '@elastic/eui';
88
import React, { useCallback, useMemo, useRef } from 'react';
99
import { FormattedMessage } from '@kbn/i18n/react';
1010

@@ -183,7 +183,6 @@ export const OpenTimeline = React.memo<OpenTimelineProps>(
183183
/>
184184

185185
<EuiPanel className={OPEN_TIMELINE_CLASS_NAME}>
186-
<EuiSpacer size="m" />
187186
{!!timelineFilter && timelineFilter}
188187
<SearchRow
189188
data-test-subj="search-row"

x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface OpenTimelineModalProps {
2222
}
2323

2424
const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
25-
const OPEN_TIMELINE_MODAL_WIDTH = 1000; // px
25+
const OPEN_TIMELINE_MODAL_WIDTH = 1100; // px
2626

2727
export const OpenTimelineModal = React.memo<OpenTimelineModalProps>(
2828
({ hideActions = [], modalTitle, onClose, onOpen }) => {

0 commit comments

Comments
 (0)