Skip to content

Commit

Permalink
[Security Solution][Timeline] Fix timeline styling and createFrom beh… (
Browse files Browse the repository at this point in the history
  • Loading branch information
patrykkopycinski authored Jul 20, 2020
1 parent 40da9e6 commit edc2f9d
Show file tree
Hide file tree
Showing 16 changed files with 297 additions and 69 deletions.
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

0 comments on commit edc2f9d

Please sign in to comment.