Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [Security Solution][Timeline] Fix timeline styling and createFrom beh… (#72152) #72439

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,3 @@ export const showAllOthersBucket: string[] = [
'destination.ip',
'user.name',
];

/*
* This should be set to true after https://github.com/elastic/kibana/pull/67496 is merged
*/
export const enableElasticFilter = false;
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const reformatDataProviderWithNewValue = <T extends DataProvider | DataPr
timelineType: TimelineType = TimelineType.default
): T => {
// Support for legacy "template-like" timeline behavior that is using hardcoded list of templateFields
if (timelineType === TimelineType.default) {
if (timelineType !== TimelineType.template) {
if (templateFields.includes(dataProvider.queryMatch.field)) {
const newValue = getStringArray(dataProvider.queryMatch.field, ecsData);
if (newValue.length) {
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/security_solution/public/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5654,6 +5654,8 @@ export namespace GetOneTimeline {

kqlQuery: Maybe<string>;

type: Maybe<DataProviderType>;

queryMatch: Maybe<_QueryMatch>;
};

Expand Down Expand Up @@ -5870,6 +5872,8 @@ export namespace PersistTimelineMutation {

eventType: Maybe<string>;

excludedRowRendererIds: Maybe<RowRendererId[]>;

favorite: Maybe<Favorite[]>;

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

kqlQuery: Maybe<string>;

type: Maybe<DataProviderType>;

queryMatch: Maybe<QueryMatch>;

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

kqlQuery: Maybe<string>;

type: Maybe<DataProviderType>;

queryMatch: Maybe<_QueryMatch>;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,203 @@ describe('helpers', () => {
width: 1100,
});
});

test('if duplicates and timeline.timelineType is not matching with outcome timelineType it should return draft with empty title', () => {
const timeline = {
savedObjectId: 'savedObject-1',
title: 'Awesome Timeline',
version: '1',
status: TimelineStatus.active,
timelineType: TimelineType.default,
};

const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.template);
expect(newTimeline).toEqual({
columns: [
{
columnHeaderType: 'not-filtered',
id: '@timestamp',
width: 190,
},
{
columnHeaderType: 'not-filtered',
id: 'message',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'event.category',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'event.action',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'host.name',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'source.ip',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'destination.ip',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'user.name',
width: 180,
},
],
dataProviders: [],
dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' },
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
filters: [],
highlightedDropAndProviderId: '',
historyIds: [],
id: 'savedObject-1',
isFavorite: false,
isLive: false,
isSelectAllChecked: false,
isLoading: false,
isSaving: false,
itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100],
kqlMode: 'filter',
kqlQuery: {
filterQuery: null,
filterQueryDraft: null,
},
loadingEventIds: [],
noteIds: [],
pinnedEventIds: {},
pinnedEventsSaveObject: {},
savedObjectId: 'savedObject-1',
selectedEventIds: {},
show: false,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
sortDirection: 'desc',
},
status: TimelineStatus.draft,
title: '',
timelineType: TimelineType.template,
templateTimelineId: null,
templateTimelineVersion: null,
version: '1',
width: 1100,
});
});

test('if duplicates and timeline.timelineType is not matching with outcome timelineType it should return draft with empty title template', () => {
const timeline = {
savedObjectId: 'savedObject-1',
title: 'Awesome Template',
version: '1',
status: TimelineStatus.active,
timelineType: TimelineType.template,
};

const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.default);
expect(newTimeline).toEqual({
columns: [
{
columnHeaderType: 'not-filtered',
id: '@timestamp',
width: 190,
},
{
columnHeaderType: 'not-filtered',
id: 'message',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'event.category',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'event.action',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'host.name',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'source.ip',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'destination.ip',
width: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'user.name',
width: 180,
},
],
dataProviders: [],
dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' },
description: '',
deletedEventIds: [],
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
filters: [],
highlightedDropAndProviderId: '',
historyIds: [],
id: 'savedObject-1',
isFavorite: false,
isLive: false,
isSelectAllChecked: false,
isLoading: false,
isSaving: false,
itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100],
kqlMode: 'filter',
kqlQuery: {
filterQuery: null,
filterQueryDraft: null,
},
loadingEventIds: [],
noteIds: [],
pinnedEventIds: {},
pinnedEventsSaveObject: {},
savedObjectId: 'savedObject-1',
selectedEventIds: {},
show: false,
showCheckboxes: false,
sort: {
columnId: '@timestamp',
sortDirection: 'desc',
},
status: TimelineStatus.draft,
title: '',
timelineType: TimelineType.default,
templateTimelineId: null,
templateTimelineVersion: null,
version: '1',
width: 1100,
});
});

test('if columns are null, we should get the default columns', () => {
const timeline = {
savedObjectId: 'savedObject-1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,29 +173,33 @@ const getTemplateTimelineId = (
duplicate: boolean,
targetTimelineType?: TimelineType
) => {
if (!duplicate) {
return timeline.templateTimelineId;
}

if (
targetTimelineType === TimelineType.default &&
timeline.timelineType === TimelineType.template
) {
return timeline.templateTimelineId;
}

// TODO: MOVE TO BACKEND
return uuid.v4();
return duplicate && timeline.timelineType === TimelineType.template
? // TODO: MOVE TO THE BACKEND
uuid.v4()
: timeline.templateTimelineId;
};

const convertToDefaultField = ({ and, ...dataProvider }: DataProviderResult) =>
deepMerge(dataProvider, {
type: DataProviderType.default,
queryMatch: {
value:
dataProvider.queryMatch!.operator === IS_OPERATOR ? '' : dataProvider.queryMatch!.value,
},
});
const convertToDefaultField = ({ and, ...dataProvider }: DataProviderResult) => {
if (dataProvider.type === DataProviderType.template) {
return deepMerge(dataProvider, {
type: DataProviderType.default,
enabled: dataProvider.queryMatch!.operator !== IS_OPERATOR,
queryMatch: {
value:
dataProvider.queryMatch!.operator === IS_OPERATOR ? '' : dataProvider.queryMatch!.value,
},
});
}

return dataProvider;
};

const getDataProviders = (
duplicate: boolean,
Expand All @@ -212,6 +216,28 @@ const getDataProviders = (
return dataProviders;
};

export const getTimelineTitle = (
timeline: TimelineResult,
duplicate: boolean,
timelineType?: TimelineType
) => {
const isCreateTimelineFromAction = timelineType && timeline.timelineType !== timelineType;
if (isCreateTimelineFromAction) return '';

return duplicate ? `${timeline.title} - Duplicate` : timeline.title || '';
};

export const getTimelineStatus = (
timeline: TimelineResult,
duplicate: boolean,
timelineType?: TimelineType
) => {
const isCreateTimelineFromAction = timelineType && timeline.timelineType !== timelineType;
if (isCreateTimelineFromAction) return TimelineStatus.draft;

return duplicate ? TimelineStatus.active : timeline.status;
};

// eslint-disable-next-line complexity
export const defaultTimelineToTimelineModel = (
timeline: TimelineResult,
Expand All @@ -234,11 +260,11 @@ export const defaultTimelineToTimelineModel = (
pinnedEventIds: setPinnedEventIds(duplicate, timeline.pinnedEventIds),
pinnedEventsSaveObject: setPinnedEventsSaveObject(duplicate, timeline.pinnedEventsSaveObject),
id: duplicate ? '' : timeline.savedObjectId,
status: duplicate ? TimelineStatus.active : timeline.status,
status: getTimelineStatus(timeline, duplicate, timelineType),
savedObjectId: duplicate ? null : timeline.savedObjectId,
version: duplicate ? null : timeline.version,
timelineType: timelineType ?? timeline.timelineType,
title: duplicate ? `${timeline.title} - Duplicate` : timeline.title || '',
title: getTimelineTitle(timeline, duplicate, timelineType),
templateTimelineId: getTemplateTimelineId(timeline, duplicate, timelineType),
templateTimelineVersion: duplicate && isTemplate ? 1 : timeline.templateTimelineVersion,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiPanel, EuiBasicTable, EuiSpacer } from '@elastic/eui';
import { EuiPanel, EuiBasicTable } from '@elastic/eui';
import React, { useCallback, useMemo, useRef } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';

Expand Down Expand Up @@ -183,7 +183,6 @@ export const OpenTimeline = React.memo<OpenTimelineProps>(
/>

<EuiPanel className={OPEN_TIMELINE_CLASS_NAME}>
<EuiSpacer size="m" />
{!!timelineFilter && timelineFilter}
<SearchRow
data-test-subj="search-row"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface OpenTimelineModalProps {
}

const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
const OPEN_TIMELINE_MODAL_WIDTH = 1000; // px
const OPEN_TIMELINE_MODAL_WIDTH = 1100; // px

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