From 684128bc6c6b8bca8461ce72cb43bfbe4e78fac1 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 5 Oct 2020 23:32:41 -0400 Subject: [PATCH] [SECURITY SOLUTION] Investigate EQL signal in timeline (#79049) (#79606) * fix template timeline for rule * fix moving column with linkfield by giving back the browserfield * leftover from investigate timeline with template from rule * add visualization for eql sequences in timeline + allow eql investigate to timeline through signal.group.id * bug fix of column in eventviewer * review I * review II * fix bug - Columns dynamically added to timeline indicate no data * fix pagination to work as attempted by elastic search * no tweak on pagination timeline * fix snapshot * reset activePage to 0 when changing indexNames * remove last page when we are not sure if it is really the last page * update activePage when resetting it by searchParameter * review bug on the last commit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/ecs/rule/index.ts | 1 + .../common/ecs/signal/index.ts | 3 + .../timeline/events/all/index.ts | 4 +- .../common/search_strategy/timeline/index.ts | 15 +--- .../public/app/home/index.tsx | 6 +- .../drag_drop_context_wrapper.tsx | 3 +- .../components/drag_and_drop/helpers.ts | 7 ++ .../drag_and_drop/provider_container.tsx | 2 - .../events_viewer/events_viewer.tsx | 18 ++--- .../components/alerts_table/actions.tsx | 66 ++++++++++------ .../alerts_table/alerts_utility_bar/index.tsx | 15 +++- .../alerts_table/default_config.tsx | 3 +- .../public/graphql/introspection.json | 52 +++++++------ .../security_solution/public/graphql/types.ts | 13 ++-- .../security_solution/public/helpers.ts | 3 +- .../components/fields_browser/field_items.tsx | 2 + .../components/fields_browser/helpers.tsx | 7 ++ .../components/notes/note_cards/index.tsx | 16 ++-- .../body/events/event_column_view.tsx | 25 +++--- .../timeline/body/events/stateful_event.tsx | 3 +- .../components/timeline/body/helpers.tsx | 3 + .../footer/__snapshots__/index.test.tsx.snap | 17 ++-- .../components/timeline/footer/index.test.tsx | 39 +++------- .../components/timeline/footer/index.tsx | 53 ++++++------- .../timeline/search_super_select/index.tsx | 8 +- .../timelines/components/timeline/styles.tsx | 21 ++++- .../components/timeline/timeline.tsx | 4 +- .../public/timelines/containers/all/index.tsx | 1 + .../public/timelines/containers/index.tsx | 77 +++++++++++++------ .../containers/one/index.gql_query.ts | 4 +- .../server/graphql/timeline/resolvers.ts | 2 +- .../server/graphql/timeline/schema.gql.ts | 2 +- .../security_solution/server/graphql/types.ts | 14 ++-- .../server/lib/timeline/saved_object.ts | 28 ++++++- .../timeline/factory/events/all/constants.ts | 2 + .../timeline/factory/events/all/helpers.ts | 17 +--- .../timeline/factory/events/all/index.ts | 25 ++---- .../events/all/query.events_all.dsl.ts | 4 +- 38 files changed, 333 insertions(+), 252 deletions(-) diff --git a/x-pack/plugins/security_solution/common/ecs/rule/index.ts b/x-pack/plugins/security_solution/common/ecs/rule/index.ts index fa200b46b37b..47d132337194 100644 --- a/x-pack/plugins/security_solution/common/ecs/rule/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/rule/index.ts @@ -41,4 +41,5 @@ export interface RuleEcs { updated_by?: string[]; version?: string[]; note?: string[]; + building_block_type?: string[]; } diff --git a/x-pack/plugins/security_solution/common/ecs/signal/index.ts b/x-pack/plugins/security_solution/common/ecs/signal/index.ts index 55a889f3a5dd..6482b892bc18 100644 --- a/x-pack/plugins/security_solution/common/ecs/signal/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/signal/index.ts @@ -10,4 +10,7 @@ export interface SignalEcs { rule?: RuleEcs; original_time?: string[]; status?: string[]; + group?: { + id?: string[]; + }; } diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts index f673fca290a2..764d8aaf6ea3 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts @@ -6,7 +6,7 @@ import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; import { Ecs } from '../../../../ecs'; -import { CursorType, Inspect, Maybe, PageInfoPaginated } from '../../../common'; +import { CursorType, Inspect, Maybe, PaginationInputPaginated } from '../../../common'; import { TimelineRequestOptionsPaginated } from '../..'; export interface TimelineEdges { @@ -29,7 +29,7 @@ export interface TimelineNonEcsData { export interface TimelineEventsAllStrategyResponse extends IEsSearchResponse { edges: TimelineEdges[]; totalCount: number; - pageInfo: PageInfoPaginated; + pageInfo: Pick; inspect?: Maybe; } diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts index 6b96783adc25..578f90561774 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts @@ -14,13 +14,7 @@ import { TimelineEventsLastEventTimeRequestOptions, TimelineEventsLastEventTimeStrategyResponse, } from './events'; -import { - DocValueFields, - PaginationInput, - PaginationInputPaginated, - TimerangeInput, - SortField, -} from '../common'; +import { DocValueFields, PaginationInputPaginated, TimerangeInput, SortField } from '../common'; export * from './events'; @@ -34,14 +28,9 @@ export interface TimelineRequestBasicOptions extends IEsSearchRequest { factoryQueryType?: TimelineFactoryQueryTypes; } -export interface TimelineRequestOptions extends TimelineRequestBasicOptions { - pagination: PaginationInput; - sort: SortField; -} - export interface TimelineRequestOptionsPaginated extends TimelineRequestBasicOptions { - pagination: PaginationInputPaginated; + pagination: Pick; sort: SortField; } diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index 68eb93f7e2fe..079c34114dfd 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -58,7 +58,11 @@ const HomePageComponent: React.FC = ({ children }) => { ); const [showTimeline] = useShowTimeline(); - const { browserFields, indexPattern, indicesExist } = useSourcererScope(); + const { browserFields, indexPattern, indicesExist } = useSourcererScope( + subPluginId.current === DETECTIONS_SUB_PLUGIN_ID + ? SourcererScopeName.detections + : SourcererScopeName.default + ); // side effect: this will attempt to upgrade the endpoint package if it is not up to date // this will run when a user navigates to the Security Solution app and when they navigate between // tabs in the app. This is useful for keeping the endpoint package as up to date as possible until diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx index 4efb662a4aab..175682aa43e7 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -126,8 +126,7 @@ export const DragDropContextWrapperComponent = React.memo diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts index 132ab054c3af..a300f253de08 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts @@ -9,6 +9,7 @@ import { DropResult } from 'react-beautiful-dnd'; import { Dispatch } from 'redux'; import { ActionCreator } from 'typescript-fsa'; +import { alertsHeaders } from '../../../detections/components/alerts_table/default_config'; import { BrowserField, BrowserFields, getAllFieldsByName } from '../../containers/source'; import { dragAndDropActions } from '../../store/actions'; import { IdToDataProvider } from '../../store/drag_and_drop/model'; @@ -17,6 +18,7 @@ import { timelineActions } from '../../../timelines/store/timeline'; import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants'; import { addContentToTimeline } from '../../../timelines/components/timeline/data_providers/helpers'; import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; +import { TimelineId } from '../../../../common/types/timeline'; export const draggableIdPrefix = 'draggableId'; @@ -197,6 +199,10 @@ export const addFieldToTimelineColumns = ({ const fieldId = getFieldIdFromDraggable(result); const allColumns = getAllFieldsByName(browserFields); const column = allColumns[fieldId]; + const initColumnHeader = + timelineId === TimelineId.detectionsPage || timelineId === TimelineId.detectionsRulesDetailsPage + ? alertsHeaders.find((c) => c.id === fieldId) ?? {} + : {}; if (column != null) { dispatch( @@ -211,6 +217,7 @@ export const addFieldToTimelineColumns = ({ type: column.type, aggregatable: column.aggregatable, width: DEFAULT_COLUMN_MIN_WIDTH, + ...initColumnHeader, }, id: timelineId, index: result.destination != null ? result.destination.index : 0, diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx index 8db6d073f968..e39633dbf328 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/provider_container.tsx @@ -66,8 +66,6 @@ const ProviderContainerComponent = styled.div` .${STATEFUL_EVENT_CSS_CLASS_NAME}:hover &, tr:hover & { - background-color: ${({ theme }) => theme.eui.euiColorLightShade}; - &::before { background-image: linear-gradient( 135deg, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 6ed9765a911d..152161e2ce3a 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import { isEmpty, union } from 'lodash/fp'; +import { isEmpty } from 'lodash/fp'; import React, { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -190,14 +190,10 @@ const EventsViewerComponent: React.FC = ({ [isLoadingIndexPattern, combinedQueries, start, end] ); - const fields = useMemo( - () => - union( - columnsHeader.map((c) => c.id), - queryFields ?? [] - ), - [columnsHeader, queryFields] - ); + const fields = useMemo(() => [...columnsHeader.map((c) => c.id), ...(queryFields ?? [])], [ + columnsHeader, + queryFields, + ]); const sortField = useMemo( () => ({ @@ -311,9 +307,7 @@ const EventsViewerComponent: React.FC = ({ itemsPerPageOptions={itemsPerPageOptions} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadPage} - serverSideEventCount={totalCountMinusDeleted} - showMorePagesIndicator={pageInfo.showMorePagesIndicator} - totalCount={pageInfo.fakeTotalCount} + totalCount={totalCountMinusDeleted} /> ) } diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 0e2aee5abd42..043a5afc4480 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -150,6 +150,12 @@ export const getThresholdAggregationDataProvider = ( ]; }; +export const isEqlRule = (ecsData: Ecs) => + ecsData.signal?.rule?.type?.length && ecsData.signal?.rule?.type[0] === 'eql'; + +export const isThresholdRule = (ecsData: Ecs) => + ecsData.signal?.rule?.type?.length && ecsData.signal?.rule?.type[0] === 'threshold'; + export const sendAlertToTimelineAction = async ({ apolloClient, createTimeline, @@ -158,13 +164,12 @@ export const sendAlertToTimelineAction = async ({ updateTimelineIsLoading, searchStrategyClient, }: SendAlertToTimelineActionProps) => { - let openAlertInBasicTimeline = true; const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; const timelineId = ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; const { to, from } = determineToAndFrom({ ecsData }); - if (timelineId !== '' && apolloClient != null) { + if (!isEmpty(timelineId) && apolloClient != null) { try { updateTimelineIsLoading({ id: TimelineId.active, isLoading: true }); const [responseTimeline, eventDataResp] = await Promise.all([ @@ -173,6 +178,7 @@ export const sendAlertToTimelineAction = async ({ fetchPolicy: 'no-cache', variables: { id: timelineId, + timelineType: TimelineType.template, }, }), searchStrategyClient.search< @@ -195,7 +201,6 @@ export const sendAlertToTimelineAction = async ({ const eventData: TimelineEventsDetailsItem[] = getOr([], 'data', eventDataResp); if (!isEmpty(resultingTimeline)) { const timelineTemplate: TimelineResult = omitTypenameInTimeline(resultingTimeline); - openAlertInBasicTimeline = false; const { timeline, notes } = formatTimelineResultToModel( timelineTemplate, true, @@ -250,16 +255,11 @@ export const sendAlertToTimelineAction = async ({ }); } } catch { - openAlertInBasicTimeline = true; updateTimelineIsLoading({ id: TimelineId.active, isLoading: false }); } } - if ( - ecsData.signal?.rule?.type?.length && - ecsData.signal?.rule?.type[0] === 'threshold' && - openAlertInBasicTimeline - ) { + if (isThresholdRule(ecsData)) { return createTimeline({ from, notes: null, @@ -312,26 +312,44 @@ export const sendAlertToTimelineAction = async ({ ruleNote: noteContent, }); } else { + let dataProviders = [ + { + and: [], + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${ecsData._id}`, + name: ecsData._id, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: '_id', + value: ecsData._id, + operator: ':' as const, + }, + }, + ]; + if (isEqlRule(ecsData)) { + const signalGroupId = ecsData.signal?.group?.id?.length + ? ecsData.signal?.group?.id[0] + : 'unknown-signal-group-id'; + dataProviders = [ + { + ...dataProviders[0], + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${signalGroupId}`, + queryMatch: { + field: 'signal.group.id', + value: signalGroupId, + operator: ':' as const, + }, + }, + ]; + } + return createTimeline({ from, notes: null, timeline: { ...timelineDefaults, - dataProviders: [ - { - and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${ecsData._id}`, - name: ecsData._id, - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: '_id', - value: ecsData._id, - operator: ':', - }, - }, - ], + dataProviders, id: TimelineId.active, indexNames: [], dateRange: { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx index 30fcdc21d61e..a7e7126eae11 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx @@ -47,6 +47,17 @@ const UtilityBarFlexGroup = styled(EuiFlexGroup)` min-width: 175px; `; +const BuildingBlockContainer = styled(EuiFlexItem)` + background: repeating-linear-gradient( + 127deg, + rgba(245, 167, 0, 0.2), + rgba(245, 167, 0, 0.2) 1px, + rgba(245, 167, 0, 0.05) 2px, + rgba(245, 167, 0, 0.05) 10px + ); + padding: ${({ theme }) => `${theme.eui.paddingSizes.xs}`}; +`; + const AlertsUtilityBarComponent: React.FC = ({ canUserCRUD, hasIndexWrite, @@ -133,7 +144,7 @@ const AlertsUtilityBarComponent: React.FC = ({ const UtilityBarAdditionalFiltersContent = (closePopover: () => void) => ( - + = ({ data-test-subj="showBuildingBlockAlertsCheckbox" label={i18n.ADDITIONAL_FILTERS_ACTIONS_SHOW_BUILDING_BLOCK} /> - + ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index eebabc59d932..a5d3a3f90767 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -165,7 +165,9 @@ export const alertsDefaultModel: SubsetTimelineModel = { export const requiredFieldsForActions = [ '@timestamp', 'signal.status', + 'signal.group.id', 'signal.original_time', + 'signal.rule.building_block_type', 'signal.rule.filters', 'signal.rule.from', 'signal.rule.language', @@ -177,7 +179,6 @@ export const requiredFieldsForActions = [ 'signal.rule.type', 'signal.original_event.kind', 'signal.original_event.module', - // Endpoint exception fields 'file.path', 'file.Ext.code_signature.subject_name', diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json index 8d780137b847..cc91d2390581 100644 --- a/x-pack/plugins/security_solution/public/graphql/introspection.json +++ b/x-pack/plugins/security_solution/public/graphql/introspection.json @@ -212,6 +212,12 @@ "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } }, "defaultValue": null + }, + { + "name": "timelineType", + "description": "", + "type": { "kind": "ENUM", "name": "TimelineType", "ofType": null }, + "defaultValue": null } ], "type": { @@ -1914,6 +1920,29 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "ENUM", + "name": "TimelineType", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "default", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "template", + "description": "", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "OBJECT", "name": "TimelineResult", @@ -2953,29 +2982,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "ENUM", - "name": "TimelineType", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "default", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "template", - "description": "", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", "name": "PageInfoTimeline", diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index 5cc8fd1f37d2..52598b5f4494 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -274,6 +274,11 @@ export enum HostPolicyResponseActionStatus { warning = 'warning', } +export enum TimelineType { + default = 'default', + template = 'template', +} + export enum DataProviderType { default = 'default', template = 'template', @@ -301,11 +306,6 @@ export enum TimelineStatus { immutable = 'immutable', } -export enum TimelineType { - default = 'default', - template = 'template', -} - export enum SortFieldTimeline { title = 'title', description = 'description', @@ -1599,6 +1599,8 @@ export interface SourceQueryArgs { } export interface GetOneTimelineQueryArgs { id: string; + + timelineType?: Maybe; } export interface GetAllTimelineQueryArgs { pageInfo: PageInfoTimeline; @@ -2166,6 +2168,7 @@ export namespace PersistTimelineNoteMutation { export namespace GetOneTimeline { export type Variables = { id: string; + timelineType?: Maybe; }; export type Query = { diff --git a/x-pack/plugins/security_solution/public/helpers.ts b/x-pack/plugins/security_solution/public/helpers.ts index 92f3d2390755..def49fd20513 100644 --- a/x-pack/plugins/security_solution/public/helpers.ts +++ b/x-pack/plugins/security_solution/public/helpers.ts @@ -110,5 +110,6 @@ export const getInspectResponse = ( prevResponse: InspectResponse ): InspectResponse => ({ dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], - response: response != null ? [JSON.stringify(response, null, 2)] : prevResponse?.response, + response: + response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx index 892243474623..a75089b58b86 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx @@ -35,6 +35,7 @@ import { OnUpdateColumns } from '../timeline/events'; import { TruncatableText } from '../../../common/components/truncatable_text'; import { FieldName } from './field_name'; import * as i18n from './translations'; +import { getAlertColumnHeader } from './helpers'; const TypeIcon = styled(EuiIcon)` margin-left: 5px; @@ -127,6 +128,7 @@ export const getFieldItems = ({ columnHeaderType: defaultColumnHeaderType, id: field.name || '', width: DEFAULT_COLUMN_MIN_WIDTH, + ...getAlertColumnHeader(timelineId, field.name || ''), }) } /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx index df4b106ab6bd..ba9ade096c4d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx @@ -7,8 +7,10 @@ import { EuiLoadingSpinner } from '@elastic/eui'; import { filter, get, pickBy } from 'lodash/fp'; import styled from 'styled-components'; +import { TimelineId } from '../../../../common/types/timeline'; import { BrowserField, BrowserFields } from '../../../common/containers/source'; +import { alertsHeaders } from '../../../detections/components/alerts_table/default_config'; import { DEFAULT_CATEGORY_NAME, defaultHeaders, @@ -141,3 +143,8 @@ export const mergeBrowserFieldsWithDefaultCategory = ( fieldIds: defaultHeaders.map((header) => header.id), }), }); + +export const getAlertColumnHeader = (timelineId: string, fieldId: string) => + timelineId === TimelineId.detectionsPage || timelineId === TimelineId.detectionsRulesDetailsPage + ? alertsHeaders.find((c) => c.id === fieldId) ?? {} + : {}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx index 9d9055e3ad74..62d169b1169d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx @@ -25,21 +25,21 @@ NoteContainer.displayName = 'NoteContainer'; interface NoteCardsCompProps { children: React.ReactNode; } +const NoteCardsCompContainer = styled(EuiPanel)` + border: none; + background-color: transparent; + box-shadow: none; +`; +NoteCardsCompContainer.displayName = 'NoteCardsCompContainer'; const NoteCardsComp = React.memo(({ children }) => ( - + {children} - + )); NoteCardsComp.displayName = 'NoteCardsComp'; const NotesContainer = styled(EuiFlexGroup)` - padding: 0 5px; margin-bottom: 5px; `; NotesContainer.displayName = 'NotesContainer'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index 3f9d230575d4..3b6585013c8d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -95,9 +95,9 @@ export const EventColumnView = React.memo( toggleShowNotes, updateNote, }) => { - const { eventType: timelineEventType, timelineType, status } = useShallowEqualSelector< - TimelineModel - >((state) => state.timeline.timelineById[timelineId]); + const { timelineType, status } = useShallowEqualSelector( + (state) => state.timeline.timelineById[timelineId] + ); const handlePinClicked = useCallback( () => @@ -151,17 +151,13 @@ export const EventColumnView = React.memo( />, ] : []), - ...(timelineEventType !== 'raw' - ? [ - , - ] - : []), + , ], [ associateNote, @@ -178,7 +174,6 @@ export const EventColumnView = React.memo( showNotes, status, timelineId, - timelineEventType, timelineType, toggleShowNotes, updateNote, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index ee68c270e9ab..be8ce5e26b3e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -33,7 +33,7 @@ import { EventsTrGroup, EventsTrSupplement, EventsTrSupplementContainer } from ' import { ColumnRenderer } from '../renderers/column_renderer'; import { getRowRenderer } from '../renderers/get_row_renderer'; import { RowRenderer } from '../renderers/row_renderer'; -import { getEventType } from '../helpers'; +import { isEventBuildingBlockType, getEventType } from '../helpers'; import { NoteCards } from '../../../notes/note_cards'; import { useEventDetailsWidthContext } from '../../../../../common/components/events_viewer/event_details_width_context'; import { EventColumnView } from './event_column_view'; @@ -183,6 +183,7 @@ const StatefulEventComponent: React.FC = ({ className={STATEFUL_EVENT_CSS_CLASS_NAME} data-test-subj="event" eventType={getEventType(event.ecs)} + isBuildingBlockType={isEventBuildingBlockType(event.ecs)} showLeftBorder={!isEventViewer} ref={divElement} > diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index cf9fbfaf1932..d4d77d6fd40a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -103,6 +103,9 @@ export const getEventIdToDataMapping = ( }; }, {}); +export const isEventBuildingBlockType = (event: Ecs): boolean => + !isEmpty(event.signal?.rule?.building_block_type); + /** Return eventType raw or signal */ export const getEventType = (event: Ecs): Omit => { if (!isEmpty(event.signal?.rule?.id)) { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/__snapshots__/index.test.tsx.snap index 2979de270fcc..35e7de298197 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/__snapshots__/index.test.tsx.snap @@ -72,15 +72,14 @@ exports[`Footer Timeline Component rendering it renders the default timeline foo data-test-subj="paging-control-container" grow={false} > - - - + { const updatedAt = 1546878704036; const serverSideEventCount = 15546; const itemsCount = 2; - const totalCount = 10; describe('rendering', () => { test('it renders the default timeline footer', () => { @@ -34,9 +33,7 @@ describe('Footer Timeline Component', () => { itemsPerPageOptions={[1, 5, 10, 20]} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadMore} - serverSideEventCount={serverSideEventCount} - totalCount={totalCount} - showMorePagesIndicator + totalCount={serverSideEventCount} /> ); @@ -57,9 +54,7 @@ describe('Footer Timeline Component', () => { itemsPerPageOptions={[1, 5, 10, 20]} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadMore} - serverSideEventCount={serverSideEventCount} - totalCount={totalCount} - showMorePagesIndicator + totalCount={serverSideEventCount} /> ); @@ -81,9 +76,7 @@ describe('Footer Timeline Component', () => { itemsPerPageOptions={[1, 5, 10, 20]} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadMore} - serverSideEventCount={serverSideEventCount} - totalCount={totalCount} - showMorePagesIndicator + totalCount={serverSideEventCount} /> ); @@ -95,6 +88,7 @@ describe('Footer Timeline Component', () => { const wrapper = shallow( { const wrapper = shallow( { itemsPerPageOptions={[1, 5, 10, 20]} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadMore} - serverSideEventCount={serverSideEventCount} - totalCount={totalCount} - showMorePagesIndicator + totalCount={serverSideEventCount} /> ); @@ -157,9 +150,7 @@ describe('Footer Timeline Component', () => { itemsPerPageOptions={[1, 5, 10, 20]} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadMore} - serverSideEventCount={serverSideEventCount} - totalCount={totalCount} - showMorePagesIndicator + totalCount={serverSideEventCount} /> ); @@ -185,9 +176,7 @@ describe('Footer Timeline Component', () => { itemsPerPageOptions={[1, 5, 10, 20]} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadMore} - serverSideEventCount={serverSideEventCount} - totalCount={totalCount} - showMorePagesIndicator + totalCount={serverSideEventCount} /> ); @@ -211,9 +200,7 @@ describe('Footer Timeline Component', () => { itemsPerPageOptions={[1, 5, 10, 20]} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadMore} - serverSideEventCount={serverSideEventCount} - totalCount={totalCount} - showMorePagesIndicator + totalCount={serverSideEventCount} /> ); @@ -239,9 +226,7 @@ describe('Footer Timeline Component', () => { itemsPerPageOptions={[1, 5, 10, 20]} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadMore} - serverSideEventCount={serverSideEventCount} - totalCount={totalCount} - showMorePagesIndicator + totalCount={serverSideEventCount} /> ); @@ -265,9 +250,7 @@ describe('Footer Timeline Component', () => { itemsPerPageOptions={[1, 5, 10, 20]} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadMore} - serverSideEventCount={serverSideEventCount} - totalCount={totalCount} - showMorePagesIndicator + totalCount={serverSideEventCount} /> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx index c06be6d50aae..4119127d5a10 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx @@ -28,7 +28,6 @@ import { OnChangeItemsPerPage, OnChangePage } from '../events'; import { LastUpdatedAt } from './last_updated'; import * as i18n from './translations'; import { useEventDetailsWidthContext } from '../../../../common/components/events_viewer/event_details_width_context'; -import { PaginationEuiFlexItem } from '../../../../common/components/paginated_table'; import { useManageTimeline } from '../../manage_timeline'; export const isCompactFooter = (width: number): boolean => width < 600; @@ -179,13 +178,23 @@ interface PagingControlProps { activePage: number; isLoading: boolean; onPageClick: OnChangePage; + totalCount: number; totalPages: number; } +const TimelinePaginationContainer = styled.div<{ hideLastPage: boolean }>` + ul.euiPagination__list { + li.euiPagination__item:last-child { + ${({ hideLastPage }) => `${hideLastPage ? 'display:none' : ''}`}; + } + } +`; + export const PagingControlComponent: React.FC = ({ activePage, isLoading, onPageClick, + totalCount, totalPages, }) => { if (isLoading) { @@ -197,12 +206,14 @@ export const PagingControlComponent: React.FC = ({ } return ( - + 9999}> + + ); }; @@ -223,8 +234,6 @@ interface FooterProps { itemsPerPageOptions: number[]; onChangeItemsPerPage: OnChangeItemsPerPage; onChangePage: OnChangePage; - serverSideEventCount: number; - showMorePagesIndicator: boolean; totalCount: number; } @@ -241,8 +250,6 @@ export const FooterComponent = ({ itemsPerPageOptions, onChangeItemsPerPage, onChangePage, - serverSideEventCount, - showMorePagesIndicator, totalCount, }: FooterProps) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -292,11 +299,6 @@ export const FooterComponent = ({ totalCount, ]); - const PaginationWrapper = useMemo( - () => (showMorePagesIndicator ? PaginationEuiFlexItem : EuiFlexItem), - [showMorePagesIndicator] - ); - useEffect(() => { if (paginationLoading && !isLoading) { setPaginationLoading(false); @@ -347,7 +349,7 @@ export const FooterComponent = ({ items={rowItems} itemsCount={itemsCount} onClick={onButtonClick} - serverSideEventCount={serverSideEventCount} + serverSideEventCount={totalCount} /> @@ -373,15 +375,14 @@ export const FooterComponent = ({ ) : ( - - - + )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx index 3019a8add4e3..1413ecc19d84 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx @@ -106,14 +106,16 @@ const SearchTimelineSuperSelectComponent: React.FC ({ export const EventsTrGroup = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__trGroup ${className}`, -}))<{ className?: string; eventType: Omit; showLeftBorder: boolean }>` +}))<{ + className?: string; + eventType: Omit; + isBuildingBlockType: boolean; + showLeftBorder: boolean; +}>` border-bottom: ${({ theme }) => theme.eui.euiBorderWidthThin} solid ${({ theme }) => theme.eui.euiColorLightShade}; - ${({ theme, eventType, showLeftBorder }) => + ${({ theme, eventType, isBuildingBlockType, showLeftBorder }) => showLeftBorder ? `border-left: 4px solid ${eventType === 'raw' ? theme.eui.euiColorLightShade : theme.eui.euiColorWarning}` : ''}; + ${({ isBuildingBlockType, showLeftBorder }) => + isBuildingBlockType + ? `background: repeating-linear-gradient(127deg, rgba(245, 167, 0, 0.2), rgba(245, 167, 0, 0.2) 1px, rgba(245, 167, 0, 0.05) 2px, rgba(245, 167, 0, 0.05) 10px);` + : ''}; &:hover { background-color: ${({ theme }) => theme.eui.euiTableHoverColor}; @@ -207,7 +216,13 @@ export const EventsTrSupplement = styled.div.attrs(({ className = '' }) => ({ }))<{ className: string }>` font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; line-height: ${({ theme }) => theme.eui.euiLineHeight}; - padding: 0 ${({ theme }) => theme.eui.paddingSizes.xs} 0 52px; + padding: 0 ${({ theme }) => theme.eui.paddingSizes.m}; + .euiAccordion + div { + background-color: ${({ theme }) => theme.eui.euiColorEmptyShade}; + padding: 0 ${({ theme }) => theme.eui.paddingSizes.s}; + border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; + border-radius: ${({ theme }) => theme.eui.paddingSizes.xs}; + } `; export const EventsTdGroupActions = styled.div.attrs(({ className = '' }) => ({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx index abc5b1401d64..b72f4f97667c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx @@ -301,9 +301,7 @@ export const TimelineComponent: React.FC = ({ itemsPerPageOptions={itemsPerPageOptions} onChangeItemsPerPage={onChangeItemsPerPage} onChangePage={loadPage} - serverSideEventCount={totalCount} - showMorePagesIndicator={pageInfo.showMorePagesIndicator} - totalCount={pageInfo.fakeTotalCount} + totalCount={totalCount} /> ) diff --git a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx index 59d7fd694563..bc38de85f288 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx @@ -93,6 +93,7 @@ export const getAllTimeline = memoizeOne( updated: timeline.updated, updatedBy: timeline.updatedBy, timelineType: timeline.timelineType ?? TimelineType.default, + templateTimelineId: timeline.templateTimelineId, })) ); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 21e290e1ede8..0ae60e6ad013 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -15,13 +15,12 @@ import { inputsModel } from '../../common/store'; import { useKibana } from '../../common/lib/kibana'; import { createFilter } from '../../common/containers/helpers'; import { DocValueFields } from '../../common/containers/query_template'; -import { generateTablePaginationOptions } from '../../common/components/paginated_table/helpers'; import { timelineActions } from '../../timelines/store/timeline'; import { detectionsTimelineIds, skipQueryForDetectionsPage } from './helpers'; import { getInspectResponse } from '../../helpers'; import { Direction, - PageInfoPaginated, + PaginationInputPaginated, TimelineEventsQueries, TimelineEventsAllStrategyResponse, TimelineEventsAllRequestOptions, @@ -37,7 +36,7 @@ export interface TimelineArgs { id: string; inspect: InspectResponse; loadPage: LoadPage; - pageInfo: PageInfoPaginated; + pageInfo: Pick; refetch: inputsModel.Refetch; totalCount: number; updatedAt: number; @@ -62,6 +61,10 @@ const getTimelineEvents = (timelineEdges: TimelineEdges[]): TimelineItem[] => timelineEdges.map((e: TimelineEdges) => e.node); const ID = 'timelineEventsQuery'; +const initSortDefault = { + field: '@timestamp', + direction: Direction.asc, +}; export const useTimelineEvents = ({ docValueFields, @@ -72,10 +75,7 @@ export const useTimelineEvents = ({ filterQuery, startDate, limit, - sort = { - field: '@timestamp', - direction: Direction.asc, - }, + sort = initSortDefault, skip = false, }: UseTimelineEventsProps): [boolean, TimelineArgs] => { const dispatch = useDispatch(); @@ -87,7 +87,7 @@ export const useTimelineEvents = ({ const [timelineRequest, setTimelineRequest] = useState( !skip ? { - fields, + fields: [], fieldRequested: fields, filterQuery: createFilter(filterQuery), id: ID, @@ -96,7 +96,10 @@ export const useTimelineEvents = ({ from: startDate, to: endDate, }, - pagination: generateTablePaginationOptions(activePage, limit), + pagination: { + activePage, + querySize: limit, + }, sort, defaultIndex: indexNames, docValueFields: docValueFields ?? [], @@ -130,8 +133,7 @@ export const useTimelineEvents = ({ totalCount: -1, pageInfo: { activePage: 0, - fakeTotalCount: 0, - showMorePagesIndicator: false, + querySize: 0, }, events: [], loadPage: wrappedLoadPage, @@ -205,31 +207,60 @@ export const useTimelineEvents = ({ } setTimelineRequest((prevRequest) => { - const myRequest = { - ...(prevRequest ?? { - fields, - fieldRequested: fields, - id, - factoryQueryType: TimelineEventsQueries.all, - }), + const prevSearchParameters = { + defaultIndex: prevRequest?.defaultIndex ?? [], + filterQuery: prevRequest?.filterQuery ?? '', + querySize: prevRequest?.pagination.querySize ?? 0, + sort: prevRequest?.sort ?? initSortDefault, + timerange: prevRequest?.timerange ?? {}, + }; + + const currentSearchParameters = { defaultIndex: indexNames, - docValueFields: docValueFields ?? [], filterQuery: createFilter(filterQuery), - id: ID, - pagination: generateTablePaginationOptions(activePage, limit), + querySize: limit, + sort, timerange: { interval: '12h', from: startDate, to: endDate, }, + }; + + const newActivePage = deepEqual(prevSearchParameters, currentSearchParameters) + ? activePage + : 0; + + const currentRequest = { + defaultIndex: indexNames, + docValueFields: docValueFields ?? [], + factoryQueryType: TimelineEventsQueries.all, + fieldRequested: fields, + fields: [], + filterQuery: createFilter(filterQuery), + id, + pagination: { + activePage: newActivePage, + querySize: limit, + }, sort, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, }; + + if (activePage !== newActivePage) { + setActivePage(newActivePage); + } + if ( !skip && !skipQueryForDetectionsPage(id, indexNames) && - !deepEqual(prevRequest, myRequest) + !deepEqual(prevRequest, currentRequest) ) { - return myRequest; + return currentRequest; } return prevRequest; }); 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 b85fbc15ce3f..fa0ecb349f9c 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 @@ -7,8 +7,8 @@ import gql from 'graphql-tag'; export const oneTimelineQuery = gql` - query GetOneTimeline($id: ID!) { - getOneTimeline(id: $id) { + query GetOneTimeline($id: ID!, $timelineType: TimelineType) { + getOneTimeline(id: $id, timelineType: $timelineType) { savedObjectId columns { aggregatable diff --git a/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts index 9bd544f6942c..9e6e9bf6ea22 100644 --- a/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts +++ b/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts @@ -44,7 +44,7 @@ export const createTimelineResolvers = ( } => ({ Query: { async getOneTimeline(root, args, { req }) { - return libs.timeline.getTimeline(req, args.id); + return libs.timeline.getTimeline(req, args.id, args.timelineType); }, async getAllTimeline(root, args, { req }) { return libs.timeline.getAllTimeline( diff --git a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts index 70596a1b41ea..2358e78b044e 100644 --- a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts @@ -317,7 +317,7 @@ export const timelineSchema = gql` ######################### extend type Query { - getOneTimeline(id: ID!): TimelineResult! + getOneTimeline(id: ID!, timelineType: TimelineType): TimelineResult! getAllTimeline(pageInfo: PageInfoTimeline!, search: String, sort: SortTimeline, onlyUserFavorite: Boolean, timelineType: TimelineType, status: TimelineStatus): ResponseTimelines! } diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts index 7d2ce8a28499..7730cea2b984 100644 --- a/x-pack/plugins/security_solution/server/graphql/types.ts +++ b/x-pack/plugins/security_solution/server/graphql/types.ts @@ -276,6 +276,11 @@ export enum HostPolicyResponseActionStatus { warning = 'warning', } +export enum TimelineType { + default = 'default', + template = 'template', +} + export enum DataProviderType { default = 'default', template = 'template', @@ -303,11 +308,6 @@ export enum TimelineStatus { immutable = 'immutable', } -export enum TimelineType { - default = 'default', - template = 'template', -} - export enum SortFieldTimeline { title = 'title', description = 'description', @@ -1601,6 +1601,8 @@ export interface SourceQueryArgs { } export interface GetOneTimelineQueryArgs { id: string; + + timelineType?: Maybe; } export interface GetAllTimelineQueryArgs { pageInfo: PageInfoTimeline; @@ -1838,6 +1840,8 @@ export namespace QueryResolvers { > = Resolver; export interface GetOneTimelineArgs { id: string; + + timelineType?: Maybe; } export type GetAllTimelineResolver< 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 23ea3e621346..83566a619061 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 @@ -58,7 +58,11 @@ export interface ResponseTemplateTimeline { } export interface Timeline { - getTimeline: (request: FrameworkRequest, timelineId: string) => Promise; + getTimeline: ( + request: FrameworkRequest, + timelineId: string, + timelineType?: TimelineTypeLiteralWithNull + ) => Promise; getAllTimeline: ( request: FrameworkRequest, @@ -95,9 +99,27 @@ export interface Timeline { export const getTimeline = async ( request: FrameworkRequest, - timelineId: string + timelineId: string, + timelineType: TimelineTypeLiteralWithNull = TimelineType.default ): Promise => { - return getSavedTimeline(request, timelineId); + let timelineIdToUse = timelineId; + try { + if (timelineType === TimelineType.template) { + const options = { + type: timelineSavedObjectType, + perPage: 1, + page: 1, + filter: `siem-ui-timeline.attributes.templateTimelineId: ${timelineId}`, + }; + const result = await getAllSavedTimeline(request, options); + if (result.totalCount === 1) { + timelineIdToUse = result.timeline[0].savedObjectId; + } + } + } catch { + // TO DO, we need to bring the logger here + } + return getSavedTimeline(request, timelineIdToUse); }; export const getTimelineByTemplateTimelineId = async ( diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts index 3f4af1fbcb36..6631484f025c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts @@ -7,6 +7,7 @@ export const TIMELINE_EVENTS_FIELDS = [ '@timestamp', 'signal.status', + 'signal.group.id', 'signal.original_time', 'signal.rule.filters', 'signal.rule.from', @@ -136,6 +137,7 @@ export const TIMELINE_EVENTS_FIELDS = [ 'signal.rule.note', 'signal.rule.threshold', 'signal.rule.exceptions_list', + 'signal.rule.building_block_type', 'suricata.eve.proto', 'suricata.eve.flow_id', 'suricata.eve.alert.signature', diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts index b2ce57d87ae6..b2e3989f99d4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts @@ -11,8 +11,7 @@ import { toArray } from '../../../../helpers/to_array'; export const formatTimelineData = ( dataFields: readonly string[], ecsFields: readonly string[], - hit: EventHit, - fieldMap: Readonly> + hit: EventHit ) => uniq([...ecsFields, ...dataFields]).reduce( (flattenedFields, fieldName) => { @@ -25,14 +24,7 @@ export const formatTimelineData = ( flattenedFields.cursor.value = hit.sort[0]; flattenedFields.cursor.tiebreaker = hit.sort[1]; } - return mergeTimelineFieldsWithHit( - fieldName, - flattenedFields, - fieldMap, - hit, - dataFields, - ecsFields - ); + return mergeTimelineFieldsWithHit(fieldName, flattenedFields, hit, dataFields, ecsFields); }, { node: { ecs: { _id: '' }, data: [], _id: '', _index: '' }, @@ -48,13 +40,12 @@ const specialFields = ['_id', '_index', '_type', '_score']; const mergeTimelineFieldsWithHit = ( fieldName: string, flattenedFields: T, - fieldMap: Readonly>, hit: { _source: {} }, dataFields: readonly string[], ecsFields: readonly string[] ) => { - if (fieldMap[fieldName] != null || dataFields.includes(fieldName)) { - const esField = dataFields.includes(fieldName) ? fieldName : fieldMap[fieldName]; + if (fieldName != null || dataFields.includes(fieldName)) { + const esField = fieldName; if (has(esField, hit._source) || specialFields.includes(esField)) { const objectWithProperty = { node: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/index.ts index 6b28fc2598d4..182197432da8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/index.ts @@ -14,10 +14,9 @@ import { TimelineEventsAllRequestOptions, TimelineEdges, } from '../../../../../../common/search_strategy/timeline'; -import { inspectStringifyObject, reduceFields } from '../../../../../utils/build_query'; +import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionTimelineFactory } from '../../types'; import { buildTimelineEventsAllQuery } from './query.events_all.dsl'; -import { eventFieldsMap } from '../../../../../lib/ecs_fields'; import { TIMELINE_EVENTS_FIELDS } from './constants'; import { formatTimelineData } from './helpers'; @@ -27,10 +26,7 @@ export const timelineEventsAll: SecuritySolutionTimelineFactory ): Promise => { const { fieldRequested, ...queryOptions } = cloneDeep(options); - queryOptions.fields = uniq([ - ...fieldRequested, - ...reduceFields(TIMELINE_EVENTS_FIELDS, eventFieldsMap), - ]); - const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; - + queryOptions.fields = uniq([...fieldRequested, ...TIMELINE_EVENTS_FIELDS]); + const { activePage, querySize } = options.pagination; const totalCount = getOr(0, 'hits.total.value', response.rawResponse); const hits = response.rawResponse.hits.hits; - const edges: TimelineEdges[] = hits.splice(cursorStart, querySize - cursorStart).map((hit) => + const edges: TimelineEdges[] = hits.map((hit) => // @ts-expect-error - formatTimelineData(options.fieldRequested, TIMELINE_EVENTS_FIELDS, hit, eventFieldsMap) + formatTimelineData(options.fieldRequested, TIMELINE_EVENTS_FIELDS, hit) ); const inspect = { dsl: [inspectStringifyObject(buildTimelineEventsAllQuery(queryOptions))], }; - const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; - const showMorePagesIndicator = totalCount > fakeTotalCount; return { ...response, @@ -63,8 +53,7 @@ export const timelineEventsAll: SecuritySolutionTimelineFactory