diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index b39a038c4cc3c..f934d90c740a5 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -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; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts index 5025d782e2aa2..084e4bff7e0ac 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts @@ -148,7 +148,7 @@ export const reformatDataProviderWithNewValue = { // 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) { diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index 5f8595df23f9b..1808d32547fbe 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -5654,6 +5654,8 @@ export namespace GetOneTimeline { kqlQuery: Maybe; + type: Maybe; + queryMatch: Maybe<_QueryMatch>; }; @@ -5870,6 +5872,8 @@ export namespace PersistTimelineMutation { eventType: Maybe; + excludedRowRendererIds: Maybe; + favorite: Maybe; filters: Maybe; @@ -5932,6 +5936,8 @@ export namespace PersistTimelineMutation { kqlQuery: Maybe; + type: Maybe; + queryMatch: Maybe; and: Maybe; @@ -5964,6 +5970,8 @@ export namespace PersistTimelineMutation { kqlQuery: Maybe; + type: Maybe; + queryMatch: Maybe<_QueryMatch>; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 5759d96b95f9e..f4bd17005fed7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -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', diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index 9899b38f445f9..af289f94c9a0d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -173,10 +173,6 @@ const getTemplateTimelineId = ( duplicate: boolean, targetTimelineType?: TimelineType ) => { - if (!duplicate) { - return timeline.templateTimelineId; - } - if ( targetTimelineType === TimelineType.default && timeline.timelineType === TimelineType.template @@ -184,18 +180,26 @@ const getTemplateTimelineId = ( 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, @@ -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, @@ -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, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx index 13786c55e2a8d..d839a1deddf21 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx @@ -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'; @@ -183,7 +183,6 @@ export const OpenTimeline = React.memo( /> - {!!timelineFilter && timelineFilter} ( ({ hideActions = [], modalTitle, onClose, onOpen }) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx index b3ae39bf8b346..af2bd53df77db 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx @@ -79,43 +79,43 @@ export const OpenTimelineModalBody = memo( selectedTimelinesCount={selectedItems.length} title={title} /> - <> - - {SearchRowContent} - - - + <> + + {SearchRowContent} + + + ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx index a95f163349f05..8c4c686698c88 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx @@ -33,9 +33,7 @@ export const useTimelineStatus = ({ templateTimelineFilter: JSX.Element[] | null; installPrepackagedTimelines: () => void; } => { - const [selectedTab, setSelectedTab] = useState( - TemplateTimelineType.elastic - ); + const [selectedTab, setSelectedTab] = useState(null); const isTemplateFilterEnabled = useMemo(() => timelineType === TimelineType.template, [ timelineType, ]); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx index af63957d35075..bf2094e7659ee 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx @@ -92,7 +92,7 @@ const ConvertFieldBadge = styled(ProviderFieldBadge)` `; const TemplateFieldBadge: React.FC = ({ type, toggleType }) => { - if (type === DataProviderType.default) { + if (type !== DataProviderType.template) { return ( {i18n.CONVERT_TO_TEMPLATE_FIELD} ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx index eb103d8e7e861..5c992fd640a97 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx @@ -258,8 +258,7 @@ export const EventsTdContent = styled.div.attrs(({ className }) => ({ ? `${width}px` : '100%'}; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */ - > button.euiButtonIcon, - > .euiToolTipAnchor > button.euiButtonIcon { + button.euiButtonIcon { margin-left: ${({ theme }) => `-${theme.eui.paddingSizes.xs}`}; } `; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts b/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts index 0aaeb22d72afc..5e50a7fb3313e 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts @@ -42,6 +42,7 @@ export const oneTimelineQuery = gql` enabled excluded kqlQuery + type queryMatch { field displayField diff --git a/x-pack/plugins/security_solution/public/timelines/containers/persist.gql_query.ts b/x-pack/plugins/security_solution/public/timelines/containers/persist.gql_query.ts index 6a0609f9158f3..c38aa67ccebb2 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/persist.gql_query.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/persist.gql_query.ts @@ -32,6 +32,7 @@ export const persistTimelineMutation = gql` enabled excluded kqlQuery + type queryMatch { field displayField @@ -45,6 +46,7 @@ export const persistTimelineMutation = gql` enabled excluded kqlQuery + type queryMatch { field displayField @@ -56,6 +58,7 @@ export const persistTimelineMutation = gql` } description eventType + excludedRowRendererIds favorite { fullName userName diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts index 2f9331ec9db8e..7757794c6dc9a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts @@ -68,6 +68,7 @@ import { updateTimeline, updateTitle, updateAutoSaveMsg, + setExcludedRowRendererIds, setFilters, setSavedQueryId, startTimelineSaving, @@ -88,9 +89,11 @@ import { ActionTimeline, TimelineEpicDependencies } from './types'; const timelineActionsType = [ applyKqlFilterQuery.type, addProvider.type, + addTimeline.type, dataProviderEdited.type, removeColumn.type, removeProvider.type, + setExcludedRowRendererIds.type, setFilters.type, setSavedQueryId.type, updateColumns.type, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts index 82a2a866a71ff..b50195219f993 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts @@ -7,7 +7,7 @@ import { getOr } from 'lodash/fp'; import { SavedObjectsFindOptions } from '../../../../../../src/core/server'; -import { UNAUTHENTICATED_USER, enableElasticFilter } from '../../../common/constants'; +import { UNAUTHENTICATED_USER } from '../../../common/constants'; import { NoteSavedObject } from '../../../common/types/timeline/note'; import { PinnedEventSavedObject } from '../../../common/types/timeline/pinned_event'; import { @@ -153,12 +153,10 @@ const getTimelineTypeFilter = ( templateTimelineType == null ? null : templateTimelineType === TemplateTimelineType.elastic - ? `siem-ui-timeline.attributes.createdBy: "Elsatic"` + ? `siem-ui-timeline.attributes.createdBy: "Elastic"` : `not siem-ui-timeline.attributes.createdBy: "Elastic"`; - const filters = enableElasticFilter - ? [typeFilter, draftFilter, immutableFilter, templateTimelineTypeFilter] - : [typeFilter, draftFilter, immutableFilter]; + const filters = [typeFilter, draftFilter, immutableFilter, templateTimelineTypeFilter]; return filters.filter((f) => f != null).join(' and '); }; diff --git a/x-pack/test/api_integration/apis/security_solution/saved_objects/timeline.ts b/x-pack/test/api_integration/apis/security_solution/saved_objects/timeline.ts index 10ba9621c0430..a399c07e31065 100644 --- a/x-pack/test/api_integration/apis/security_solution/saved_objects/timeline.ts +++ b/x-pack/test/api_integration/apis/security_solution/saved_objects/timeline.ts @@ -114,6 +114,7 @@ export default function ({ getService }: FtrProviderContext) { enabled: true, excluded: false, kqlQuery: '', + type: 'default', queryMatch: { field: 'host.name', displayField: null,