From 5438e0ba3e2d0994dee603f70a5d16c856794821 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Tue, 17 Dec 2024 09:16:30 +0100 Subject: [PATCH 01/48] incremental --- .../hooks/timeline/use_init_timeline_url_param.ts | 4 ++++ .../public/common/store/global_url_param/reducer.ts | 2 -- .../public/detections/pages/alerts/index.tsx | 5 +++++ .../timelines/components/open_timeline/helpers.ts | 12 ++++++++++++ .../public/timelines/pages/timelines_page.tsx | 3 ++- 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts index 9c3133daf93c6..f350c27c82952 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts @@ -25,6 +25,7 @@ export const useInitTimelineFromUrlParam = () => { const onInitialize = useCallback( (initialState: TimelineUrl | null) => { + console.log({ initialState }); if (initialState != null) { queryTimelineById({ activeTimelineTab: initialState.activeTab, @@ -33,6 +34,7 @@ export const useInitTimelineFromUrlParam = () => { timelineId: initialState.id, openTimeline: initialState.isOpen, savedSearchId: initialState.savedSearchId, + eventId: initialState.eventId, }); } }, @@ -42,6 +44,8 @@ export const useInitTimelineFromUrlParam = () => { useEffect(() => { const listener = () => { const timelineState = new URLSearchParams(window.location.search).get(URL_PARAM_KEY.timeline); + console.log({ timelineState }); + debugger; if (!timelineState) { return; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/store/global_url_param/reducer.ts b/x-pack/solutions/security/plugins/security_solution/public/common/store/global_url_param/reducer.ts index 47b1ffbfea076..7986ba16aadec 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/store/global_url_param/reducer.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/store/global_url_param/reducer.ts @@ -18,8 +18,6 @@ export const globalUrlParamReducer = reducerWithInitialState(initialGlobalUrlPar .case(registerUrlParam, (state, { key, initialValue }) => { // It doesn't allow the query param to be used twice if (state[key] !== undefined) { - // eslint-disable-next-line no-console - console.error(`Url param key '${key}' is already being used.`); return state; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/index.tsx index 997f627cca872..352b37897af60 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/index.tsx @@ -20,6 +20,7 @@ import { DetectionEnginePage } from '../detection_engine/detection_engine'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import { useReadonlyHeader } from '../../../use_readonly_header'; import { AlertDetailsRedirect } from './alert_details_redirect'; +import { EventDetailsRedirect } from './event_details_redirect'; const AlertsRoute = () => ( @@ -33,6 +34,10 @@ const AlertsContainerComponent: React.FC = () => { return ( + {/* Redirect to the alerts page filtered for the given alert id */} diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index 46ab60d9324be..a7f69d1241941 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -299,6 +299,7 @@ export interface QueryTimelineById { onOpenTimeline?: (timeline: TimelineModel) => void; openTimeline?: boolean; savedSearchId?: string; + eventId?: string; } export const useQueryTimelineById = () => { @@ -315,8 +316,18 @@ export const useQueryTimelineById = () => { onOpenTimeline, openTimeline = true, savedSearchId, + eventId, }: QueryTimelineById) => { if (timelineId == null) { + const queryExpr = eventId ? `_id:"${eventId}"` : ''; + let kqlQuery = null; + if (eventId) { + kqlQuery = { + filterQuery: { + kuery: { kind: 'kuery', expression: queryExpr }, + }, + }; + } updateTimeline({ id: TimelineId.active, duplicate: false, @@ -327,6 +338,7 @@ export const useQueryTimelineById = () => { ...timelineDefaults, columns: defaultUdtHeaders, id: TimelineId.active, + kqlQuery, activeTab: activeTimelineTab, show: openTimeline, initialized: true, diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx index d213eeaf8f5f0..bfe08892eef84 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -23,7 +23,8 @@ import { SecurityRoutePageWrapper } from '../../common/components/security_route export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; export const TimelinesPage = React.memo(() => { - const { tabName } = useParams<{ pageName: SecurityPageName; tabName: string }>(); + const { tabName, pageName } = useParams<{ pageName: SecurityPageName; tabName: string }>(); + const { indicesExist } = useSourcererDataView(); const { timelinePrivileges: { crud: canWriteTimeline }, From 20e27766acb3570b2b06a3bd708e81307bc3c483 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Tue, 17 Dec 2024 15:00:56 +0100 Subject: [PATCH 02/48] incremental save --- .../security/utils/index.tsx | 41 +++++++++++++++++++ .../timeline/use_init_timeline_url_param.ts | 3 +- .../components/open_timeline/helpers.ts | 18 +++----- .../public/timelines/store/model.ts | 2 + 4 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx new file mode 100644 index 0000000000000..31e8bb48f7245 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { encode } from '@kbn/rison'; + +export interface TimelineRedirectArgs { + from?: string; + to?: string; + query?: string; +} +export const getSecurityTimelineRedirectUrl = ({ from, to, query }: TimelineRedirectArgs) => { + const BASE_PATH = '/app/security/timelines'; + + let timelineTimerangeSearchParam = {}; + if (from && to) { + timelineTimerangeSearchParam = { + timeline: { + from, + to, + linkTo: false, + }, + }; + } + + const timelineSearchParam = { + activeTab: 'query', + query, + open: true, + }; + + const encodedTimelineParam = encode(timelineSearchParam); + const encodedTimelineTimerangeParam = encode(timelineTimerangeSearchParam); + + return `${BASE_PATH}?timeline=${encodedTimelineParam}&timeRange=${encodedTimelineTimerangeParam}`; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts index f350c27c82952..95d48730a0148 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts @@ -25,7 +25,6 @@ export const useInitTimelineFromUrlParam = () => { const onInitialize = useCallback( (initialState: TimelineUrl | null) => { - console.log({ initialState }); if (initialState != null) { queryTimelineById({ activeTimelineTab: initialState.activeTab, @@ -34,7 +33,7 @@ export const useInitTimelineFromUrlParam = () => { timelineId: initialState.id, openTimeline: initialState.isOpen, savedSearchId: initialState.savedSearchId, - eventId: initialState.eventId, + query: initialState.query, }); } }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index a7f69d1241941..82ea7022d4878 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -10,7 +10,7 @@ import { getOr } from 'lodash/fp'; import { v4 as uuidv4 } from 'uuid'; import deepMerge from 'deepmerge'; import { useDiscoverInTimelineContext } from '../../../common/components/discover_in_timeline/use_discover_in_timeline_context'; -import type { ColumnHeaderOptions } from '../../../../common/types/timeline'; +import type { ColumnHeaderOptions, KueryFilterQuery } from '../../../../common/types/timeline'; import type { TimelineResponse, ColumnHeaderResult, @@ -299,7 +299,8 @@ export interface QueryTimelineById { onOpenTimeline?: (timeline: TimelineModel) => void; openTimeline?: boolean; savedSearchId?: string; - eventId?: string; + /* Lucene or Kql query */ + query?: KueryFilterQuery; } export const useQueryTimelineById = () => { @@ -316,18 +317,9 @@ export const useQueryTimelineById = () => { onOpenTimeline, openTimeline = true, savedSearchId, - eventId, + query, }: QueryTimelineById) => { if (timelineId == null) { - const queryExpr = eventId ? `_id:"${eventId}"` : ''; - let kqlQuery = null; - if (eventId) { - kqlQuery = { - filterQuery: { - kuery: { kind: 'kuery', expression: queryExpr }, - }, - }; - } updateTimeline({ id: TimelineId.active, duplicate: false, @@ -338,7 +330,7 @@ export const useQueryTimelineById = () => { ...timelineDefaults, columns: defaultUdtHeaders, id: TimelineId.active, - kqlQuery, + kqlQuery: query, activeTab: activeTimelineTab, show: openTimeline, initialized: true, diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/store/model.ts b/x-pack/solutions/security/plugins/security_solution/public/timelines/store/model.ts index f93d6b3f6b649..b0fefa7dbb004 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/store/model.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/store/model.ts @@ -17,6 +17,7 @@ import type { DataProvider, SerializedFilterQuery, TimelineEventsType, + KueryFilterQuery, } from '../../../common/types/timeline'; import type { RowRendererId, @@ -203,4 +204,5 @@ export interface TimelineUrl { isOpen: boolean; graphEventId?: string; savedSearchId?: string; + query?: KueryFilterQuery; } From cb34563129949e11953a1244a318d16a28f77bec Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Thu, 21 Nov 2024 15:35:44 +0100 Subject: [PATCH 03/48] poc: Overview tab --- .../register_profile_providers.ts | 2 + .../profiles/document_profile.ts | 1 + .../security_document_profile/profile.tsx | 145 ++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts index ffeab1853f390..d47e2e3337426 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -31,6 +31,7 @@ import { createDeprecationLogsDataSourceProfileProvider } from './common/depreca import { createClassicNavRootProfileProvider } from './common/classic_nav_root_profile'; import { createObservabilityTracesSpanDocumentProfileProvider } from './observability/traces_document_profile/span_document_profile'; import { createObservabilityTracesTransactionDocumentProfileProvider } from './observability/traces_document_profile/transaction_document_profile'; +import { createSecurityDocumentProfileProvider } from './security/security_document_profile/profile'; /** * Register profile providers for root, data source, and document contexts to the profile profile services @@ -152,4 +153,5 @@ const createDocumentProfileProviders = (providerServices: ProfileProviderService createObservabilityLogDocumentProfileProvider(providerServices), createObservabilityTracesSpanDocumentProfileProvider(providerServices), createObservabilityTracesTransactionDocumentProfileProvider(providerServices), + createSecurityDocumentProfileProvider(), ]; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts b/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts index 8f7350d863716..446c26c302e2a 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts @@ -22,6 +22,7 @@ export enum DocumentType { Span = 'span', Transaction = 'transaction', Default = 'default', + Alert = 'alert', } /** diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx new file mode 100644 index 0000000000000..bda31019784a6 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { getFieldValue } from '@kbn/discover-utils'; +import { + EuiAccordion, + EuiButton, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiTitle, + htmlIdGenerator, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { DocumentProfileProvider, DocumentType } from '../../../profiles'; + +export const createSecurityDocumentProfileProvider = (): DocumentProfileProvider => ({ + profileId: 'security-document-profile', + profile: { + getDocViewer: (prev) => (params) => { + const prevDocViewer = prev(params); + + const description = getFieldValue(params.record, 'kibana.alert.rule.description'); + const reason = getFieldValue(params.record, 'kibana.alert.reason'); + const note = getFieldValue(params.record, 'kibana.alert.rule.note'); + const alertURl = getFieldValue(params.record, 'kibana.alert.url'); + + return { + ...prevDocViewer, + docViewsRegistry: (registry) => { + registry.add({ + id: 'doc_view_alerts_overview', + title: 'Alert Overview', + order: 0, + component: (props) => { + return ( + + + + {description} + + + + + {reason} + + + + + + } + aria-label={i18n.translate( + 'xpack.securitySolution.flyout.right.investigation.investigationGuide.previewAriaLabel', + { defaultMessage: 'Investigation guide' } + )} + > + + + + + + + Open in Security + + + + ); + }, + }); + + return prevDocViewer.docViewsRegistry(registry); + }, + }; + }, + }, + resolve: (params) => { + if (getFieldValue(params.record, 'event.kind') !== 'signal') { + console.log('Not - Security Document Profile matched'); + return { isMatch: false }; + } + + console.log('Security Document Profile matched'); + + return { + isMatch: true, + context: { + type: DocumentType.Alert, + }, + }; + }, +}); +const idPrefix = htmlIdGenerator()(); +function ExpandableSection({ title, children }) { + const [trigger, setTrigger] = useState('open'); + + const onToggle = (isOpen) => { + const newState = isOpen ? 'open' : 'closed'; + setTrigger(newState); + setID(`${idPrefix}--${newState}`); + }; + + return ( + +

{title}

+ + } + > + + + {children} + +
+ ); +} From 986be47206d894ff618c53bbb5cec5c50fbf44a7 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Wed, 18 Dec 2024 13:57:59 +0100 Subject: [PATCH 04/48] feat: timeline redirect basic logic --- .../accessors/get_alert_event_overview.tsx | 127 +++++++++++++++ .../security_document_profile/profile.tsx | 112 +------------- .../security/translations.ts | 29 ++++ .../security/utils/index.tsx | 39 ++++- .../timeline/use_sync_timeline_url_param.ts | 11 +- .../public/detections/pages/alerts/index.tsx | 5 - .../right/utils/event_utils.tsx | 7 +- .../components/open_timeline/helpers.ts | 7 +- .../discover/context_awareness/doc_viewer.ts | 146 ++++++++++++++++++ .../ftr/discover/context_awareness/index.ts | 3 +- 10 files changed, 363 insertions(+), 123 deletions(-) create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx create mode 100644 src/plugins/discover/public/context_awareness/profile_providers/security/translations.ts create mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx new file mode 100644 index 0000000000000..1178be02d3910 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { useMemo, useState } from 'react'; +import type { FC, PropsWithChildren } from 'react'; +import { getFieldValue } from '@kbn/discover-utils'; +import { DocViewerComponent } from '@kbn/unified-doc-viewer/src/services/types'; +import { + EuiTitle, + EuiSpacer, + EuiAccordion, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiText, +} from '@elastic/eui'; +import { EcsFlat } from '@elastic/ecs'; +import * as i18n from '../translations'; +import { getSecurityTimelineRedirectUrl } from '../utils'; + +export interface AllowedValue { + description?: string; + expected_event_types?: string[]; + name?: string; +} + +/** + * Helper function to return the description of an allowed value of the specified field + * @param fieldName + * @param value + * @returns ecs description of the value + */ +export const getEcsAllowedValueDescription = (value: string): string => { + const allowedValues: AllowedValue[] = EcsFlat['event.category']?.allowed_values ?? []; + const result = + allowedValues?.find((item) => item.name === value)?.description ?? i18n.noEcsDescriptionReason; + return result; +}; + +export const ExpandableSection: FC> = ({ + title, + children, +}) => { + const [trigger, setTrigger] = useState<'open' | 'closed'>('open'); + + const onToggle = (isOpen: boolean) => { + const newState = isOpen ? 'open' : 'closed'; + setTrigger(newState); + }; + + return ( + +

{title}

+ + } + > + + + {children} + +
+ ); +}; + +export const AlertEventOverview: DocViewerComponent = ({ hit }) => { + const reason = useMemo(() => getFieldValue(hit, 'kibana.alert.reason') as string, [hit]); + const alertURL = useMemo(() => getFieldValue(hit, 'kibana.alert.url') as string, [hit]); + const eventKind = useMemo(() => getFieldValue(hit, 'event.kind') as string, [hit]); + const isAlert = useMemo(() => eventKind === 'signal', [eventKind]); + const eventId = useMemo(() => getFieldValue(hit, '_id') as string, [hit]); + const eventURL = useMemo( + () => + getSecurityTimelineRedirectUrl({ + from: getFieldValue(hit, '@timestamp') as string, + to: getFieldValue(hit, '@timestamp') as string, + eventId: eventId as string, + index: getFieldValue(hit, '_index') as string, + }), + [hit, eventId] + ); + + const eventCategory = useMemo(() => getFieldValue(hit, 'event.category') as string, [hit]); + + return ( + + + + {getEcsAllowedValueDescription(eventCategory)} + + + {isAlert ? ( + + + {reason} + + + ) : null} + + + {i18n.overviewExploreButtonLabel(isAlert)} + + + + ); +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx index bda31019784a6..193f288b29ebd 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx @@ -7,91 +7,26 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; import { getFieldValue } from '@kbn/discover-utils'; -import { - EuiAccordion, - EuiButton, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, - EuiTitle, - htmlIdGenerator, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { DocumentProfileProvider, DocumentType } from '../../../profiles'; +import { DocumentProfileProvider, DocumentType, SolutionType } from '../../../profiles'; +import { AlertEventOverview } from '../accessors/get_alert_event_overview'; +import * as i18n from '../translations'; export const createSecurityDocumentProfileProvider = (): DocumentProfileProvider => ({ profileId: 'security-document-profile', profile: { getDocViewer: (prev) => (params) => { const prevDocViewer = prev(params); - - const description = getFieldValue(params.record, 'kibana.alert.rule.description'); - const reason = getFieldValue(params.record, 'kibana.alert.reason'); - const note = getFieldValue(params.record, 'kibana.alert.rule.note'); - const alertURl = getFieldValue(params.record, 'kibana.alert.url'); + const isAlert = getFieldValue(params.record, 'event.kind') === 'signal'; return { ...prevDocViewer, docViewsRegistry: (registry) => { registry.add({ id: 'doc_view_alerts_overview', - title: 'Alert Overview', + title: i18n.overviewTabTitle(isAlert), order: 0, - component: (props) => { - return ( - - - - {description} - - - - - {reason} - - - - - - } - aria-label={i18n.translate( - 'xpack.securitySolution.flyout.right.investigation.investigationGuide.previewAriaLabel', - { defaultMessage: 'Investigation guide' } - )} - > - - - - - - - Open in Security - - - - ); - }, + component: AlertEventOverview, }); return prevDocViewer.docViewsRegistry(registry); @@ -100,46 +35,15 @@ export const createSecurityDocumentProfileProvider = (): DocumentProfileProvider }, }, resolve: (params) => { - if (getFieldValue(params.record, 'event.kind') !== 'signal') { - console.log('Not - Security Document Profile matched'); + if (params.rootContext.solutionType !== SolutionType.Security) { return { isMatch: false }; } - console.log('Security Document Profile matched'); - return { isMatch: true, context: { - type: DocumentType.Alert, + type: DocumentType.Default, }, }; }, }); -const idPrefix = htmlIdGenerator()(); -function ExpandableSection({ title, children }) { - const [trigger, setTrigger] = useState('open'); - - const onToggle = (isOpen) => { - const newState = isOpen ? 'open' : 'closed'; - setTrigger(newState); - setID(`${idPrefix}--${newState}`); - }; - - return ( - -

{title}

- - } - > - - - {children} - -
- ); -} diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/translations.ts b/src/plugins/discover/public/context_awareness/profile_providers/security/translations.ts new file mode 100644 index 0000000000000..56624c6a1b11b --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/security/translations.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { i18n } from '@kbn/i18n'; + +export const overviewTabTitle = (isAlert: boolean) => + i18n.translate('discover.profile.security.flyout.overviewTabTitle', { + values: { isAlert }, + defaultMessage: '{isAlert, select, true {Alerts Overview} other {Event Overview}}', + }); + +export const overviewExploreButtonLabel = (isAlert: boolean) => + i18n.translate('discover.profile.security.flyout.overviewExploreButtonLabel', { + values: { isAlert }, + defaultMessage: `Explore ${isAlert ? 'Alert' : 'Event'} in Security`, + }); + +export const noEcsDescriptionReason = i18n.translate( + 'discover.profile.security.flyout.noEventKindDescriptionMessage', + { + defaultMessage: "This field doesn't have a description because it's not part of ECS.", + } +); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx index 31e8bb48f7245..2a4886247d990 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -9,13 +9,25 @@ import { encode } from '@kbn/rison'; +export interface CustomQuery { + kind: 'kuery' | 'lucene'; + expression: string; +} + export interface TimelineRedirectArgs { from?: string; to?: string; - query?: string; + eventId?: string; + index: string; } -export const getSecurityTimelineRedirectUrl = ({ from, to, query }: TimelineRedirectArgs) => { - const BASE_PATH = '/app/security/timelines'; + +export const getSecurityTimelineRedirectUrl = ({ + from, + to, + index, + eventId, +}: TimelineRedirectArgs) => { + const BASE_PATH = '/app/security/alerts'; let timelineTimerangeSearchParam = {}; if (from && to) { @@ -28,14 +40,31 @@ export const getSecurityTimelineRedirectUrl = ({ from, to, query }: TimelineRedi }; } + const query: CustomQuery = { + kind: 'kuery', + expression: `_id: ${eventId}`, + }; + const timelineSearchParam = { activeTab: 'query', query, - open: true, + isOpen: true, + }; + + const timelineFlyoutSearchParam = { + right: { + id: 'document-details-right', + params: { + id: eventId, + indexName: index, + scopeId: 'timeline-1', + }, + }, }; const encodedTimelineParam = encode(timelineSearchParam); const encodedTimelineTimerangeParam = encode(timelineTimerangeSearchParam); + const encodedTimelineFlyoutParam = encode(timelineFlyoutSearchParam); - return `${BASE_PATH}?timeline=${encodedTimelineParam}&timeRange=${encodedTimelineTimerangeParam}`; + return `${BASE_PATH}?timeline=${encodedTimelineParam}&timeRange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_sync_timeline_url_param.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_sync_timeline_url_param.ts index 4d3aa88c4eaf5..fc483c5f76e1d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_sync_timeline_url_param.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_sync_timeline_url_param.ts @@ -17,9 +17,8 @@ import { URL_PARAM_KEY } from '../use_url_state'; export const useSyncTimelineUrlParam = () => { const updateUrlParam = useUpdateUrlParam(URL_PARAM_KEY.timeline); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { activeTab, graphEventId, show, savedObjectId, savedSearchId } = useShallowEqualSelector( - (state) => getTimeline(state, TimelineId.active) ?? {} - ); + const { activeTab, graphEventId, show, savedObjectId, savedSearchId, kqlQuery } = + useShallowEqualSelector((state) => getTimeline(state, TimelineId.active) ?? {}); useEffect(() => { const params = { @@ -28,7 +27,11 @@ export const useSyncTimelineUrlParam = () => { activeTab, graphEventId: graphEventId ?? '', savedSearchId: savedSearchId ? savedSearchId : undefined, + query: { + kind: kqlQuery?.filterQuery?.kuery?.kind ?? 'kuery', + expression: kqlQuery?.filterQuery?.kuery?.expression ?? '', + }, }; updateUrlParam(params); - }, [activeTab, graphEventId, savedObjectId, show, updateUrlParam, savedSearchId]); + }, [activeTab, graphEventId, savedObjectId, show, updateUrlParam, savedSearchId, kqlQuery]); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/index.tsx index 352b37897af60..997f627cca872 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/index.tsx @@ -20,7 +20,6 @@ import { DetectionEnginePage } from '../detection_engine/detection_engine'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import { useReadonlyHeader } from '../../../use_readonly_header'; import { AlertDetailsRedirect } from './alert_details_redirect'; -import { EventDetailsRedirect } from './event_details_redirect'; const AlertsRoute = () => ( @@ -34,10 +33,6 @@ const AlertsContainerComponent: React.FC = () => { return ( - {/* Redirect to the alerts page filtered for the given alert id */} diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/utils/event_utils.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/utils/event_utils.tsx index 5f39d73bd31f9..16672867457be 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/utils/event_utils.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/utils/event_utils.tsx @@ -41,10 +41,11 @@ export const isEcsAllowedValue = ( */ export const getEcsAllowedValueDescription = (fieldName: FieldName, value: string): string => { const allowedValues: AllowedValue[] = EcsFlat[fieldName]?.allowed_values ?? []; - return ( + const result = allowedValues?.find((item) => item.name === value)?.description ?? i18n.translate('xpack.securitySolution.flyout.right.about.noEventKindDescriptionMessage', { defaultMessage: "This field doesn't have a description because it's not part of ECS.", - }) - ); + }); + + return result; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index 82ea7022d4878..4aedb77f6aa18 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -330,7 +330,12 @@ export const useQueryTimelineById = () => { ...timelineDefaults, columns: defaultUdtHeaders, id: TimelineId.active, - kqlQuery: query, + kqlQuery: { + filterQuery: { + kuery: query ?? null, + serializedQuery: query?.expression ?? '', + }, + }, activeTab: activeTimelineTab, show: openTimeline, initialized: true, diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts new file mode 100644 index 0000000000000..fffe09229dfa8 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import path from 'path'; +import { encode } from '@kbn/rison'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { SECURITY_ES_ARCHIVES_DIR } from '../../../constants'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + const esArchiver = getService('esArchiver'); + const queryBar = getService('queryBar'); + const esClient = getService('es'); + + describe('security root profile', () => { + before(async () => { + await PageObjects.svlCommonPage.loginAsViewer(); + }); + + describe('doc viewer', () => { + describe('events', () => { + before(async () => { + await esArchiver.loadIfNeeded(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); + }); + + after(async () => { + await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); + }); + + describe('DataView mode', () => { + it('should open event overview tab', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.createFromSearchBar({ + name: 'auditbeat-2022', + adHoc: true, + hasTimeField: true, + }); + await queryBar.setQuery('host.name: "siem-kibana"'); + await queryBar.clickQuerySubmitButton(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const expandDocViewerButton = await testSubjects.find('docTableExpandToggleColumn'); + await expandDocViewerButton.click(); + + await testSubjects.existOrFail('eventOverview', { timeout: 2500 }); + }); + }); + + describe('ES|QL mode', () => { + it('should open event overview tab', async () => { + const state = encode({ + datasource: { type: 'esql' }, + query: { esql: 'from auditbeat-2022 | where host.name == "siem-kibana"' }, + }); + + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const expanddocviewerbutton = await testSubjects.find('doctableexpandtogglecolumn'); + await expanddocviewerbutton.click(); + + await testSubjects.existOrFail('eventoverview', { timeout: 2500 }); + }); + }); + }); + + describe('alerts', () => { + before(async () => { + await PageObjects.svlCommonPage.loginAsAdmin(); + // // sleep + // try { + // const alias = await esClient.indices.get({ + // index: '.alerts-security.alerts-default', + // }); + // + // try { + // const stream = await esClient.indices.getDataStream({ + // name: '.alerts-security.alerts-default', + // }); + // } catch (err) {} + // + // await esClient.indices.deleteDataStream({ + // name: '.alerts-security.alerts-default', + // }); + + // .deleteAlias({ + // name: '.alerts-security.alerts-default', + // index: '.alerts-security.alerts-default-000001', + // }); + await esArchiver.load(path.join(SECURITY_ES_ARCHIVES_DIR, 'ransomware_detection'), { + useCreate: true, + }); + }); + + after(async () => { + await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'query_alert')); + }); + + describe('DataView mode', () => { + it('should open alert overview tab', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.createFromSearchBar({ + name: '.alerts-*', + adHoc: true, + hasTimeField: true, + }); + await queryBar.clickQuerySubmitButton(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const expandDocViewerButton = await testSubjects.find('docTableExpandToggleColumn'); + await expandDocViewerButton.click(); + + await testSubjects.existOrFail('alertOverview', { timeout: 2500 }); + }); + + describe('ES|QL mode', () => { + it('should open alert overview tab', async () => { + const state = encode({ + datasource: { type: 'esql' }, + query: { esql: 'from .alert-*' }, + }); + + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const expandDocViewerButton = await testSubjects.find('doctableexpandtogglecolumn'); + await expandDocViewerButton.click(); + + await testSubjects.existOrFail('alertOverview', { timeout: 2500 }); + }); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts index 4a168c5f77928..351025b929425 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts @@ -39,6 +39,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); }); - loadTestFile(require.resolve('./cell_renderer')); + // loadTestFile(require.resolve('./cell_renderer')); + loadTestFile(require.resolve('./doc_viewer')); }); } From 6b16fdabc26a0c4ecd1f7df7b3f7716c4f954f3b Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Wed, 18 Dec 2024 14:10:29 +0100 Subject: [PATCH 05/48] feat: default columns + row indicator --- .../accessors/get_default_app_state.ts | 46 +++++++++++++ .../security/accessors/get_row_indicator.ts | 30 +++++++++ .../profile_providers/security/constants.ts | 13 ++++ .../security_root_profile/profile.tsx | 24 ++++--- .../security_document_profile/profile.tsx | 67 ++++++++++--------- .../cell_renderers/cell_renderers.tsx | 8 ++- 6 files changed, 149 insertions(+), 39 deletions(-) create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts new file mode 100644 index 0000000000000..16b5d4c05a18e --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { DefaultAppStateExtension } from '../../../types'; + +export const getDefaultSecuritySolutionAppState: () => DefaultAppStateExtension = () => ({ + breakdownField: 'kibana.alert.workflow_status', + columns: [ + { + name: '@timestamp', + width: 218, + }, + { + name: 'kibana.alert.workflow_status', + width: 218, + }, + { + name: 'message', + width: 360, + }, + { + name: 'event.category', + }, + { + name: 'event.action', + }, + { + name: 'host.name', + }, + { + name: 'source.ip', + }, + { + name: 'destination.ip', + }, + { + name: 'user.name', + }, + ], +}); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts new file mode 100644 index 0000000000000..1bee669f81f14 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { getFieldValue } from '@kbn/discover-utils'; +import { UnifiedDataTableProps } from '@kbn/unified-data-table'; + +export const getAlertEventRowIndicator: UnifiedDataTableProps['getRowIndicator'] = ( + row, + euiTheme +) => { + let eventColor = euiTheme.colors.backgroundLightText; + let rowLabel = 'event'; + + if (getFieldValue(row, 'event.kind') === 'signal') { + // alert + eventColor = euiTheme.colors.warning; + rowLabel = 'alert'; + } + + return { + color: eventColor, + label: rowLabel, + }; +}; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts new file mode 100644 index 0000000000000..296a9e0ad32ae --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export const SECURITY_PROFILE_ID = { + root: 'security-root-profile', + document: 'security-document-profile', +}; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx index a17a7bc73501c..1a259f3cc51fc 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx @@ -7,15 +7,15 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { FunctionComponent, PropsWithChildren } from 'react'; -import React from 'react'; -import type { DataGridCellValueElementProps } from '@kbn/unified-data-table'; -import type { RootProfileProvider } from '../../../profiles'; -import { SolutionType } from '../../../profiles'; -import type { ProfileProviderServices } from '../../profile_provider_services'; -import type { SecurityProfileProviderFactory } from '../types'; +import React, { FunctionComponent, PropsWithChildren } from 'react'; +import { DataGridCellValueElementProps } from '@kbn/unified-data-table'; +import { RootProfileProvider, SolutionType } from '../../../profiles'; +import { ProfileProviderServices } from '../../profile_provider_services'; +import { SecurityProfileProviderFactory } from '../types'; import { createCellRendererAccessor } from '../accessors/get_cell_renderer_accessor'; import { createAppWrapperAccessor } from '../accessors/create_app_wrapper_accessor'; +import { getDefaultSecuritySolutionAppState } from '../accessors/get_default_app_state'; +import { getAlertEventRowIndicator } from '../accessors/get_row_indicator'; interface SecurityRootProfileContext { appWrapper?: FunctionComponent>; @@ -50,11 +50,19 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< (prev, { context }) => (params) => { const entries = prev(params); - ['host.name', 'user.name', 'source.ip', 'destination.ip'].forEach((fieldName) => { + [ + 'host.name', + 'user.name', + 'source.ip', + 'destination.ip', + 'kibana.alert.workflow_status', + ].forEach((fieldName) => { entries[fieldName] = context.getCellRenderer?.(fieldName) ?? entries[fieldName]; }); return entries; }, + getRowIndicatorProvider: () => () => getAlertEventRowIndicator, + getDefaultAppState: () => () => getDefaultSecuritySolutionAppState(), }, resolve: async (params) => { if (params.solutionNavId !== SolutionType.Security) { diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx index 193f288b29ebd..99f31cfc3e6a5 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx @@ -9,41 +9,48 @@ import { getFieldValue } from '@kbn/discover-utils'; import { DocumentProfileProvider, DocumentType, SolutionType } from '../../../profiles'; +import { ProfileProviderServices } from '../../profile_provider_services'; +import { SecurityProfileProviderFactory } from '../types'; +import { SECURITY_PROFILE_ID } from '../constants'; import { AlertEventOverview } from '../accessors/get_alert_event_overview'; import * as i18n from '../translations'; -export const createSecurityDocumentProfileProvider = (): DocumentProfileProvider => ({ - profileId: 'security-document-profile', - profile: { - getDocViewer: (prev) => (params) => { - const prevDocViewer = prev(params); - const isAlert = getFieldValue(params.record, 'event.kind') === 'signal'; +export const createSecurityDocumentProfileProvider: SecurityProfileProviderFactory< + DocumentProfileProvider +> = (services: ProfileProviderServices) => { + return { + profileId: SECURITY_PROFILE_ID.document, + profile: { + getDocViewer: (prev) => (params) => { + const prevDocViewer = prev(params); + const isAlert = getFieldValue(params.record, 'event.kind') === 'signal'; - return { - ...prevDocViewer, - docViewsRegistry: (registry) => { - registry.add({ - id: 'doc_view_alerts_overview', - title: i18n.overviewTabTitle(isAlert), - order: 0, - component: AlertEventOverview, - }); + return { + ...prevDocViewer, + docViewsRegistry: (registry) => { + registry.add({ + id: 'doc_view_alerts_overview', + title: i18n.overviewTabTitle(isAlert), + order: 0, + component: AlertEventOverview, + }); + + return prevDocViewer.docViewsRegistry(registry); + }, + }; + }, + }, + resolve: ({ record, rootContext }) => { + if (rootContext.solutionType !== SolutionType.Security) { + return { isMatch: false }; + } - return prevDocViewer.docViewsRegistry(registry); + return { + isMatch: true, + context: { + type: DocumentType.Default, }, }; }, - }, - resolve: (params) => { - if (params.rootContext.solutionType !== SolutionType.Security) { - return { isMatch: false }; - } - - return { - isMatch: true, - context: { - type: DocumentType.Default, - }, - }; - }, -}); + }; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx b/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx index 11ef7e73c5c85..ecf63eea6cc55 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx @@ -18,7 +18,13 @@ export type SecuritySolutionRowCellRendererGetter = Awaited< ReturnType >; -const ALLOWED_DISCOVER_RENDERED_FIELDS = ['host.name', 'user.name', 'source.ip', 'destination.ip']; +const ALLOWED_DISCOVER_RENDERED_FIELDS = [ + 'host.name', + 'user.name', + 'source.ip', + 'destination.ip', + 'kibana.alert.workflow_status', +]; export const getCellRendererForGivenRecord: SecuritySolutionRowCellRendererGetter = ( fieldName: string From 7364df3d24dc2572112f7119f94faa17f3516660 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:28:31 +0000 Subject: [PATCH 06/48] [CI] Auto-commit changed files from 'node scripts/notice' --- src/platform/plugins/shared/discover/tsconfig.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/plugins/shared/discover/tsconfig.json b/src/platform/plugins/shared/discover/tsconfig.json index 1a00f2ea16245..6c3d9797bbd95 100644 --- a/src/platform/plugins/shared/discover/tsconfig.json +++ b/src/platform/plugins/shared/discover/tsconfig.json @@ -102,7 +102,10 @@ "@kbn/response-ops-rule-form", "@kbn/embeddable-enhanced-plugin", "@kbn/shared-ux-page-analytics-no-data-types", - "@kbn/core-application-browser-mocks" + "@kbn/core-application-browser-mocks", + "@kbn/rison" ], - "exclude": ["target/**/*"] + "exclude": [ + "target/**/*" + ] } From 137c4a43796a103cc0a33e5c8c5ec03e620dd18d Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Wed, 18 Dec 2024 14:54:19 +0100 Subject: [PATCH 07/48] =?UTF-8?q?make=20ci=20=F0=9F=9F=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile_providers/register_profile_providers.ts | 2 -- .../security/accessors/get_alert_event_overview.tsx | 1 + .../profile_providers/security/translations.ts | 2 +- .../public/common/hooks/timeline/use_init_timeline_url_param.ts | 2 -- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts index d47e2e3337426..ffeab1853f390 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -31,7 +31,6 @@ import { createDeprecationLogsDataSourceProfileProvider } from './common/depreca import { createClassicNavRootProfileProvider } from './common/classic_nav_root_profile'; import { createObservabilityTracesSpanDocumentProfileProvider } from './observability/traces_document_profile/span_document_profile'; import { createObservabilityTracesTransactionDocumentProfileProvider } from './observability/traces_document_profile/transaction_document_profile'; -import { createSecurityDocumentProfileProvider } from './security/security_document_profile/profile'; /** * Register profile providers for root, data source, and document contexts to the profile profile services @@ -153,5 +152,4 @@ const createDocumentProfileProviders = (providerServices: ProfileProviderService createObservabilityLogDocumentProfileProvider(providerServices), createObservabilityTracesSpanDocumentProfileProvider(providerServices), createObservabilityTracesTransactionDocumentProfileProvider(providerServices), - createSecurityDocumentProfileProvider(), ]; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx index 1178be02d3910..49772189ec7e9 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx @@ -56,6 +56,7 @@ export const ExpandableSection: FC> = ({ return ( export const overviewExploreButtonLabel = (isAlert: boolean) => i18n.translate('discover.profile.security.flyout.overviewExploreButtonLabel', { values: { isAlert }, - defaultMessage: `Explore ${isAlert ? 'Alert' : 'Event'} in Security`, + defaultMessage: 'Explore {isAlert, select, true {Alert} other {Event}} in Security', }); export const noEcsDescriptionReason = i18n.translate( diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts index 95d48730a0148..f03474f225036 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts @@ -43,8 +43,6 @@ export const useInitTimelineFromUrlParam = () => { useEffect(() => { const listener = () => { const timelineState = new URLSearchParams(window.location.search).get(URL_PARAM_KEY.timeline); - console.log({ timelineState }); - debugger; if (!timelineState) { return; From 68bb47786cedf00740d88105ddc39dcc65ad7392 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Wed, 18 Dec 2024 15:21:18 +0100 Subject: [PATCH 08/48] fix: types --- .../security/accessors/get_alert_event_overview.tsx | 11 +++++++++++ .../public/timelines/pages/timelines_page.tsx | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx index 49772189ec7e9..d5eb308658de1 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx @@ -75,6 +75,10 @@ export const ExpandableSection: FC> = ({ export const AlertEventOverview: DocViewerComponent = ({ hit }) => { const reason = useMemo(() => getFieldValue(hit, 'kibana.alert.reason') as string, [hit]); + const description = useMemo( + () => getFieldValue(hit, 'kibana.alert.rule.description') as string, + [hit] + ); const alertURL = useMemo(() => getFieldValue(hit, 'kibana.alert.url') as string, [hit]); const eventKind = useMemo(() => getFieldValue(hit, 'event.kind') as string, [hit]); const isAlert = useMemo(() => eventKind === 'signal', [eventKind]); @@ -104,6 +108,13 @@ export const AlertEventOverview: DocViewerComponent = ({ hit }) => { {getEcsAllowedValueDescription(eventCategory)} + {description ? ( + + + {description} + + + ) : null} {isAlert ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx index bfe08892eef84..7c1d7d3f6a1a5 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -23,7 +23,7 @@ import { SecurityRoutePageWrapper } from '../../common/components/security_route export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; export const TimelinesPage = React.memo(() => { - const { tabName, pageName } = useParams<{ pageName: SecurityPageName; tabName: string }>(); + const { tabName } = useParams<{ pageName: SecurityPageName; tabName: string }>(); const { indicesExist } = useSourcererDataView(); const { From 19e2eb31ccb8488f3b18b6048ad926f297acee9e Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 3 Feb 2025 12:10:22 +0100 Subject: [PATCH 09/48] tests: add --- .../accessors/alert_event_overview.test.tsx | 79 +++++++++++ .../accessors/alert_event_overview.tsx | 123 ++++++++++++++++++ .../accessors/get_alert_event_overview.tsx | 32 ++--- .../accessors/get_row_indicator.test.ts | 53 ++++++++ .../security/accessors/get_row_indicator.ts | 2 +- .../security/utils/index.tsx | 17 +++ 6 files changed, 281 insertions(+), 25 deletions(-) create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx new file mode 100644 index 0000000000000..ffb80333f4dae --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { AlertEventOverview } from './alert_event_overview'; +import { DataTableRecord } from '@kbn/discover-utils'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { EcsFlat } from '@elastic/ecs'; + +const mockRow = { + 'kibana.alert.reason': 'test-reason', + 'kibana.alert.rule.description': 'test-description', + 'event.kind': 'signal', + _id: 'test-id', + 'kibana.alert.url': 'test-url', +}; + +const mockHit = { + flattened: mockRow, +} as unknown as DataTableRecord; + +const mockDataView = dataViewMock; + +describe('AlertEventOverviewAccessor', () => { + describe('expandable sections', () => { + test('should return the expandable sections correctly', () => { + render(); + expect(screen.getByTestId('expandableHeader-About')).toBeVisible(); + expect(screen.getByTestId('expandableContent-About')).toBeVisible(); + + fireEvent.click(screen.getByTestId('expandableHeader-About')); + expect(screen.getByTestId('expandableContent-About')).not.toBeVisible(); + }); + + test('should show expected sections', () => { + render(); + expect(screen.getByTestId('expandableHeader-About')).toBeVisible(); + + expect(screen.getByTestId('expandableHeader-Description')).toBeVisible(); + expect(screen.getByTestId('expandableContent-Description')).toHaveTextContent( + 'test-description' + ); + + expect(screen.getByTestId('expandableHeader-Reason')).toBeVisible(); + expect(screen.getByTestId('expandableContent-Reason')).toHaveTextContent('test-reason'); + + expect(screen.getByTestId('exploreSecurity')).toBeVisible(); + + expect(screen.getByTestId('exploreSecurity').getAttribute('href')).toBe('test-url'); + }); + }); + + describe('data', () => { + test.only('should return Ecs description for different event types correctly', () => { + const localMockHit = { + flattened: { + ...mockRow, + 'event.category': 'process', + }, + } as unknown as DataTableRecord; + + render(); + + expect(screen.getByTestId('expandableContent-About')).toHaveTextContent( + EcsFlat['event.category'].allowed_values.find((i) => i.name === 'process') + ?.description as string + ); + }); + + test('should display timeline redirect url correctly', () => {}); + }); +}); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx new file mode 100644 index 0000000000000..77d09357b9b9f --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { useMemo, useState } from 'react'; +import type { FC, PropsWithChildren } from 'react'; +import { getFieldValue } from '@kbn/discover-utils'; +import { DocViewerComponent } from '@kbn/unified-doc-viewer/src/services/types'; +import { + EuiTitle, + EuiSpacer, + EuiAccordion, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiText, +} from '@elastic/eui'; +import * as i18n from '../translations'; +import { getEcsAllowedValueDescription, getSecurityTimelineRedirectUrl } from '../utils'; + +export const ExpandableSection: FC> = ({ + title, + children, +}) => { + const [trigger, setTrigger] = useState<'open' | 'closed'>('open'); + + const onToggle = (isOpen: boolean) => { + const newState = isOpen ? 'open' : 'closed'; + setTrigger(newState); + }; + + return ( + +

{title}

+ + } + > + + + {children} + +
+ ); +}; + +export const AlertEventOverview: DocViewerComponent = ({ hit }) => { + const reason = useMemo(() => getFieldValue(hit, 'kibana.alert.reason') as string, [hit]); + const description = useMemo( + () => getFieldValue(hit, 'kibana.alert.rule.description') as string, + [hit] + ); + const alertURL = useMemo(() => getFieldValue(hit, 'kibana.alert.url') as string, [hit]); + const eventKind = useMemo(() => getFieldValue(hit, 'event.kind') as string, [hit]); + const isAlert = useMemo(() => eventKind === 'signal', [eventKind]); + const eventId = useMemo(() => getFieldValue(hit, '_id') as string, [hit]); + const eventURL = useMemo( + () => + getSecurityTimelineRedirectUrl({ + from: getFieldValue(hit, '@timestamp') as string, + to: getFieldValue(hit, '@timestamp') as string, + eventId: eventId as string, + index: getFieldValue(hit, '_index') as string, + }), + [hit, eventId] + ); + + const eventCategory = useMemo(() => getFieldValue(hit, 'event.category') as string, [hit]); + + return ( + + + + {getEcsAllowedValueDescription(eventCategory)} + + + {description ? ( + + + {description} + + + ) : null} + {isAlert ? ( + + + {reason} + + + ) : null} + + + {i18n.overviewExploreButtonLabel(isAlert)} + + + + ); +}; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx index d5eb308658de1..77d09357b9b9f 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx @@ -20,28 +20,8 @@ import { EuiFlexItem, EuiText, } from '@elastic/eui'; -import { EcsFlat } from '@elastic/ecs'; import * as i18n from '../translations'; -import { getSecurityTimelineRedirectUrl } from '../utils'; - -export interface AllowedValue { - description?: string; - expected_event_types?: string[]; - name?: string; -} - -/** - * Helper function to return the description of an allowed value of the specified field - * @param fieldName - * @param value - * @returns ecs description of the value - */ -export const getEcsAllowedValueDescription = (value: string): string => { - const allowedValues: AllowedValue[] = EcsFlat['event.category']?.allowed_values ?? []; - const result = - allowedValues?.find((item) => item.name === value)?.description ?? i18n.noEcsDescriptionReason; - return result; -}; +import { getEcsAllowedValueDescription, getSecurityTimelineRedirectUrl } from '../utils'; export const ExpandableSection: FC> = ({ title, @@ -56,17 +36,21 @@ export const ExpandableSection: FC> = ({ return ( +

{title}

} > - + {children}
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts new file mode 100644 index 0000000000000..d34e7efd63d42 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { DataTableRecord } from '@kbn/discover-utils'; +import { getAlertEventRowIndicator } from './get_row_indicator'; +import { EuiThemeComputed } from '@elastic/eui'; + +describe('getAlertEventRowIndicator', () => { + it('should return the correct color and label for an event row', () => { + const row = { + 'event.kind': 'event', + } as unknown as DataTableRecord; + + const euiTheme = { + colors: { + backgroundLightText: 'backgroundLightText', + }, + } as const as EuiThemeComputed; + + const result = getAlertEventRowIndicator(row, euiTheme); + + expect(result).toEqual({ + color: 'backgroundLightText', + label: 'event', + }); + }); + + it('should return the correct color and label for an alert row', () => { + const row = { + 'event.kind': 'signal', + } as unknown as DataTableRecord; + + const euiTheme = { + colors: { + backgroundLightText: 'backgroundLightText', + warning: 'warning', + }, + } as const as EuiThemeComputed; + + const result = getAlertEventRowIndicator(row, euiTheme); + + expect(result).toEqual({ + color: 'warning', + label: 'alert', + }); + }); +}); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts index 1bee669f81f14..b8040842bb75d 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts @@ -10,7 +10,7 @@ import { getFieldValue } from '@kbn/discover-utils'; import { UnifiedDataTableProps } from '@kbn/unified-data-table'; -export const getAlertEventRowIndicator: UnifiedDataTableProps['getRowIndicator'] = ( +export const getAlertEventRowIndicator: NonNullable = ( row, euiTheme ) => { diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx index 2a4886247d990..beb9d459c6e8b 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -8,12 +8,16 @@ */ import { encode } from '@kbn/rison'; +import { EcsFlat } from '@elastic/ecs'; +import * as i18n from '../translations'; export interface CustomQuery { kind: 'kuery' | 'lucene'; expression: string; } +export type EcsAllowedValue = (typeof EcsFlat)['event.category']['allowed_values'][0]; + export interface TimelineRedirectArgs { from?: string; to?: string; @@ -68,3 +72,16 @@ export const getSecurityTimelineRedirectUrl = ({ return `${BASE_PATH}?timeline=${encodedTimelineParam}&timeRange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; }; + +/** + * Helper function to return the description of an allowed value of the specified field + * @param fieldName + * @param value + * @returns ecs description of the value + */ +export const getEcsAllowedValueDescription = (value: string): string => { + const allowedValues: EcsAllowedValue[] = EcsFlat['event.category']?.allowed_values ?? []; + const result = + allowedValues?.find((item) => item.name === value)?.description ?? i18n.noEcsDescriptionReason; + return result; +}; From 05849472af17aefc249f7543da6c121d7bdc83a7 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 3 Feb 2025 12:10:49 +0100 Subject: [PATCH 10/48] test: unfocus --- .../security/accessors/alert_event_overview.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx index ffb80333f4dae..a57a905219d86 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx @@ -58,7 +58,7 @@ describe('AlertEventOverviewAccessor', () => { }); describe('data', () => { - test.only('should return Ecs description for different event types correctly', () => { + test('should return Ecs description for different event types correctly', () => { const localMockHit = { flattened: { ...mockRow, From 665b15aba67d7ee9eedd89e831151bb07b5f3b88 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 17 Feb 2025 09:15:22 +0100 Subject: [PATCH 11/48] fix: timeline redirect url + tests --- .../accessors/alert_event_overview.test.tsx | 63 ++++++++++++++++++- .../accessors/alert_event_overview.tsx | 12 +++- .../security_document_profile/profile.tsx | 7 ++- .../security/utils/index.tsx | 6 +- 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx index a57a905219d86..419560f83b7e7 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx @@ -13,12 +13,27 @@ import { AlertEventOverview } from './alert_event_overview'; import { DataTableRecord } from '@kbn/discover-utils'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { EcsFlat } from '@elastic/ecs'; +import { useDiscoverServices } from '../../../../hooks/use_discover_services'; +import { encode } from '@kbn/rison'; + +jest.mock('../../../../hooks/use_discover_services'); + +const TEST_TIMELINE_URL = 'test-timeline-url'; + +const mockGetUrlForApp = jest.fn().mockReturnValue(TEST_TIMELINE_URL); + +const mockDiscoverServices = { + application: { + getUrlForApp: mockGetUrlForApp, + }, +}; const mockRow = { 'kibana.alert.reason': 'test-reason', 'kibana.alert.rule.description': 'test-description', 'event.kind': 'signal', _id: 'test-id', + '@timestamp': '2021-08-02T14:00:00.000Z', 'kibana.alert.url': 'test-url', }; @@ -29,6 +44,9 @@ const mockHit = { const mockDataView = dataViewMock; describe('AlertEventOverviewAccessor', () => { + beforeEach(() => { + (useDiscoverServices as jest.Mock).mockReturnValue(mockDiscoverServices); + }); describe('expandable sections', () => { test('should return the expandable sections correctly', () => { render(); @@ -74,6 +92,49 @@ describe('AlertEventOverviewAccessor', () => { ); }); - test('should display timeline redirect url correctly', () => {}); + test('should display timeline redirect url correctly', () => { + const localMockHit = { + flattened: { + ...mockRow, + 'event.kind': 'event', + 'event.category': 'process', + }, + } as unknown as DataTableRecord; + render(); + const expectedURLJSON = { + timeline: { + activeTab: 'query', + isOpen: true, + query: { + expression: '_id: test-id', + kind: 'kuery', + }, + }, + + timeRange: { + timeline: { + from: mockRow['@timestamp'], + to: mockRow['@timestamp'], + linkTo: false, + }, + }, + + timelineFlyout: { + right: { + id: 'document-details-right', + params: { + id: 'test-id', + scopeId: 'timeline-1', + }, + }, + }, + }; + + expect(screen.getByTestId('exploreSecurity').getAttribute('href')).toBe( + `test-timeline-url?timeline=${encode(expectedURLJSON.timeline)}&timeRange=${encode( + expectedURLJSON.timeRange + )}&timelineFlyout=${encode(expectedURLJSON.timelineFlyout)}` + ); + }); }); }); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx index 77d09357b9b9f..00f6bc4dbb985 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx @@ -22,6 +22,7 @@ import { } from '@elastic/eui'; import * as i18n from '../translations'; import { getEcsAllowedValueDescription, getSecurityTimelineRedirectUrl } from '../utils'; +import { useDiscoverServices } from '../../../../hooks/use_discover_services'; export const ExpandableSection: FC> = ({ title, @@ -58,6 +59,14 @@ export const ExpandableSection: FC> = ({ }; export const AlertEventOverview: DocViewerComponent = ({ hit }) => { + const { + application: { getUrlForApp }, + } = useDiscoverServices(); + + const timelinesURL = getUrlForApp('securitySolutionUI', { + path: 'timelines', + }); + const reason = useMemo(() => getFieldValue(hit, 'kibana.alert.reason') as string, [hit]); const description = useMemo( () => getFieldValue(hit, 'kibana.alert.rule.description') as string, @@ -74,8 +83,9 @@ export const AlertEventOverview: DocViewerComponent = ({ hit }) => { to: getFieldValue(hit, '@timestamp') as string, eventId: eventId as string, index: getFieldValue(hit, '_index') as string, + baseURL: timelinesURL, }), - [hit, eventId] + [hit, eventId, timelinesURL] ); const eventCategory = useMemo(() => getFieldValue(hit, 'event.category') as string, [hit]); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx index 99f31cfc3e6a5..cad096f742841 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx @@ -12,14 +12,15 @@ import { DocumentProfileProvider, DocumentType, SolutionType } from '../../../pr import { ProfileProviderServices } from '../../profile_provider_services'; import { SecurityProfileProviderFactory } from '../types'; import { SECURITY_PROFILE_ID } from '../constants'; -import { AlertEventOverview } from '../accessors/get_alert_event_overview'; +import { AlertEventOverview } from '../accessors/alert_event_overview'; import * as i18n from '../translations'; export const createSecurityDocumentProfileProvider: SecurityProfileProviderFactory< DocumentProfileProvider -> = (services: ProfileProviderServices) => { +> = (_services: ProfileProviderServices) => { return { profileId: SECURITY_PROFILE_ID.document, + experimental: true, profile: { getDocViewer: (prev) => (params) => { const prevDocViewer = prev(params); @@ -40,7 +41,7 @@ export const createSecurityDocumentProfileProvider: SecurityProfileProviderFacto }; }, }, - resolve: ({ record, rootContext }) => { + resolve: ({ rootContext }) => { if (rootContext.solutionType !== SolutionType.Security) { return { isMatch: false }; } diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx index beb9d459c6e8b..1b98656a6f985 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -23,6 +23,7 @@ export interface TimelineRedirectArgs { to?: string; eventId?: string; index: string; + baseURL: string; } export const getSecurityTimelineRedirectUrl = ({ @@ -30,9 +31,8 @@ export const getSecurityTimelineRedirectUrl = ({ to, index, eventId, + baseURL, }: TimelineRedirectArgs) => { - const BASE_PATH = '/app/security/alerts'; - let timelineTimerangeSearchParam = {}; if (from && to) { timelineTimerangeSearchParam = { @@ -70,7 +70,7 @@ export const getSecurityTimelineRedirectUrl = ({ const encodedTimelineTimerangeParam = encode(timelineTimerangeSearchParam); const encodedTimelineFlyoutParam = encode(timelineFlyoutSearchParam); - return `${BASE_PATH}?timeline=${encodedTimelineParam}&timeRange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; + return `${baseURL}?timeline=${encodedTimelineParam}&timeRange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; }; /** From f100b30706639307ddda99609f6771c91f83b906 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 17 Feb 2025 11:33:56 +0100 Subject: [PATCH 12/48] fix: timerange sync issue --- .../security/accessors/alert_event_overview.tsx | 4 ++-- .../profile_providers/security/utils/index.tsx | 10 ++++++---- .../search_bar/use_init_timerange_url_params.ts | 16 ++++++++-------- .../timeline/use_init_timeline_url_param.ts | 2 +- .../public/common/hooks/use_url_state.ts | 4 ++-- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx index 00f6bc4dbb985..99cc63c1236c4 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx @@ -64,7 +64,7 @@ export const AlertEventOverview: DocViewerComponent = ({ hit }) => { } = useDiscoverServices(); const timelinesURL = getUrlForApp('securitySolutionUI', { - path: 'timelines', + path: 'alerts', }); const reason = useMemo(() => getFieldValue(hit, 'kibana.alert.reason') as string, [hit]); @@ -119,7 +119,7 @@ export const AlertEventOverview: DocViewerComponent = ({ hit }) => { { if (initialState != null) { const globalLinkTo: LinkTo = { linkTo: get('global.linkTo', initialState) }; - const globalType: TimeRangeKinds = get('global.timerange.kind', initialState); + const globalTimerangeKind: TimeRangeKinds = get('global.timerange.kind', initialState); const timelineLinkTo: LinkTo = { linkTo: get('timeline.linkTo', initialState) }; - const timelineType: TimeRangeKinds = get('timeline.timerange.kind', initialState); + const timelineTimerangeKind: TimeRangeKinds = get('timeline.timerange.kind', initialState); const socTrendsLinkTo: LinkTo = { linkTo: get('socTrends.linkTo', initialState) }; const socTrendsType: TimeRangeKinds = get('socTrends.timerange.kind', initialState); @@ -76,8 +76,8 @@ const initializeTimerangeFromUrlParam = ( dispatch(inputsActions.addLinkTo([InputsModelId.global, InputsModelId.timeline])); } - if (timelineType) { - if (timelineType === 'absolute') { + if (timelineTimerangeKind) { + if (timelineTimerangeKind === 'absolute') { const absoluteRange = normalizeTimeRange( get('timeline.timerange', initialState) ); @@ -90,7 +90,7 @@ const initializeTimerangeFromUrlParam = ( ); } - if (timelineType === 'relative') { + if (timelineTimerangeKind === 'relative') { const relativeRange = normalizeTimeRange( get('timeline.timerange', initialState) ); @@ -110,8 +110,8 @@ const initializeTimerangeFromUrlParam = ( } } - if (globalType) { - if (globalType === 'absolute') { + if (globalTimerangeKind) { + if (globalTimerangeKind === 'absolute') { const absoluteRange = normalizeTimeRange( get('global.timerange', initialState) ); @@ -123,7 +123,7 @@ const initializeTimerangeFromUrlParam = ( }) ); } - if (globalType === 'relative') { + if (globalTimerangeKind === 'relative') { const relativeRange = normalizeTimeRange( get('global.timerange', initialState) ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts index f03474f225036..4108fd4cff1c7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/timeline/use_init_timeline_url_param.ts @@ -50,7 +50,7 @@ export const useInitTimelineFromUrlParam = () => { const parsedState = safeDecode(timelineState) as TimelineUrl | null; - // Make sure we only re-initialize the timeline if there are siginificant changes to the active timeline. + // Make sure we only re-initialize the timeline if there are significant changes to the active timeline. // Without this check, we could potentially overwrite the timeline. if (!hasTimelineStateChanged(activeTimeline, parsedState)) { onInitialize(parsedState); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_url_state.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_url_state.ts index 47168d76e8ec3..467049b4aa17b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_url_state.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_url_state.ts @@ -16,11 +16,11 @@ import { useQueryTimelineByIdOnUrlChange } from './timeline/use_query_timeline_b export const useUrlState = () => { useSyncGlobalQueryString(); useInitSearchBarFromUrlParams(); - useInitTimerangeFromUrlParam(); - useUpdateTimerangeOnPageChange(); useInitTimelineFromUrlParam(); useSyncTimelineUrlParam(); useQueryTimelineByIdOnUrlChange(); + useInitTimerangeFromUrlParam(); + useUpdateTimerangeOnPageChange(); }; export const URL_PARAM_KEY = { From 79c24901fc9f4e6cced7981b6072feebc6e641c2 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 17 Feb 2025 11:48:39 +0100 Subject: [PATCH 13/48] fix: housekeeping --- .../public/common/store/global_url_param/reducer.ts | 2 ++ .../flyout/document_details/right/utils/event_utils.tsx | 7 +++---- .../public/one_discover/cell_renderers/cell_renderers.tsx | 6 ++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/store/global_url_param/reducer.ts b/x-pack/solutions/security/plugins/security_solution/public/common/store/global_url_param/reducer.ts index 7986ba16aadec..47b1ffbfea076 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/store/global_url_param/reducer.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/store/global_url_param/reducer.ts @@ -18,6 +18,8 @@ export const globalUrlParamReducer = reducerWithInitialState(initialGlobalUrlPar .case(registerUrlParam, (state, { key, initialValue }) => { // It doesn't allow the query param to be used twice if (state[key] !== undefined) { + // eslint-disable-next-line no-console + console.error(`Url param key '${key}' is already being used.`); return state; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/utils/event_utils.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/utils/event_utils.tsx index 16672867457be..5f39d73bd31f9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/utils/event_utils.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/utils/event_utils.tsx @@ -41,11 +41,10 @@ export const isEcsAllowedValue = ( */ export const getEcsAllowedValueDescription = (fieldName: FieldName, value: string): string => { const allowedValues: AllowedValue[] = EcsFlat[fieldName]?.allowed_values ?? []; - const result = + return ( allowedValues?.find((item) => item.name === value)?.description ?? i18n.translate('xpack.securitySolution.flyout.right.about.noEventKindDescriptionMessage', { defaultMessage: "This field doesn't have a description because it's not part of ECS.", - }); - - return result; + }) + ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx b/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx index ecf63eea6cc55..1259e519a68c6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx @@ -18,6 +18,12 @@ export type SecuritySolutionRowCellRendererGetter = Awaited< ReturnType >; +/** + * + * This controls the list of fields that are allowed custom security solution rendering + * in Discover's contextual View + * + */ const ALLOWED_DISCOVER_RENDERED_FIELDS = [ 'host.name', 'user.name', From 0073e2fd1d272cd48aa5499c0f5fa9cc43ef4e35 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 17 Feb 2025 13:07:20 +0100 Subject: [PATCH 14/48] fix: rebase issues --- .../security/security_document_profile/profile.tsx | 0 .../profile_providers/security/translations.ts | 0 .../profile_providers/security/utils/index.tsx | 14 ++++++-------- 3 files changed, 6 insertions(+), 8 deletions(-) rename src/{plugins => platform/plugins/shared}/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx (100%) rename src/{plugins => platform/plugins/shared}/discover/public/context_awareness/profile_providers/security/translations.ts (100%) rename src/{plugins => platform/plugins/shared}/discover/public/context_awareness/profile_providers/security/utils/index.tsx (89%) diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx similarity index 100% rename from src/plugins/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx rename to src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/translations.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts similarity index 100% rename from src/plugins/discover/public/context_awareness/profile_providers/security/translations.ts rename to src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts diff --git a/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx similarity index 89% rename from src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx rename to src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx index e6c1bc254ca5c..beb9d459c6e8b 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -23,7 +23,6 @@ export interface TimelineRedirectArgs { to?: string; eventId?: string; index: string; - baseURL: string; } export const getSecurityTimelineRedirectUrl = ({ @@ -31,17 +30,16 @@ export const getSecurityTimelineRedirectUrl = ({ to, index, eventId, - baseURL, }: TimelineRedirectArgs) => { + const BASE_PATH = '/app/security/alerts'; + let timelineTimerangeSearchParam = {}; if (from && to) { timelineTimerangeSearchParam = { timeline: { - timerange: { - from, - to, - kind: 'absolute', - }, + from, + to, + linkTo: false, }, }; } @@ -72,7 +70,7 @@ export const getSecurityTimelineRedirectUrl = ({ const encodedTimelineTimerangeParam = encode(timelineTimerangeSearchParam); const encodedTimelineFlyoutParam = encode(timelineFlyoutSearchParam); - return `${baseURL}?timeline=${encodedTimelineParam}&timerange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; + return `${BASE_PATH}?timeline=${encodedTimelineParam}&timeRange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; }; /** From aef1bd41a2500d727d7a82abc131c7a4f3e7b6bd Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 17 Feb 2025 13:20:54 +0100 Subject: [PATCH 15/48] fix: more rebase issues --- .../accessors/alert_event_overview.test.tsx | 10 ++++++---- .../profile_providers/security/utils/index.tsx | 14 ++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx index 419560f83b7e7..f1b08a12599e0 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx @@ -113,9 +113,11 @@ describe('AlertEventOverviewAccessor', () => { timeRange: { timeline: { - from: mockRow['@timestamp'], - to: mockRow['@timestamp'], - linkTo: false, + timerange: { + from: mockRow['@timestamp'], + to: mockRow['@timestamp'], + kind: 'absolute', + }, }, }, @@ -131,7 +133,7 @@ describe('AlertEventOverviewAccessor', () => { }; expect(screen.getByTestId('exploreSecurity').getAttribute('href')).toBe( - `test-timeline-url?timeline=${encode(expectedURLJSON.timeline)}&timeRange=${encode( + `test-timeline-url?timeline=${encode(expectedURLJSON.timeline)}&timerange=${encode( expectedURLJSON.timeRange )}&timelineFlyout=${encode(expectedURLJSON.timelineFlyout)}` ); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx index beb9d459c6e8b..e6c1bc254ca5c 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -23,6 +23,7 @@ export interface TimelineRedirectArgs { to?: string; eventId?: string; index: string; + baseURL: string; } export const getSecurityTimelineRedirectUrl = ({ @@ -30,16 +31,17 @@ export const getSecurityTimelineRedirectUrl = ({ to, index, eventId, + baseURL, }: TimelineRedirectArgs) => { - const BASE_PATH = '/app/security/alerts'; - let timelineTimerangeSearchParam = {}; if (from && to) { timelineTimerangeSearchParam = { timeline: { - from, - to, - linkTo: false, + timerange: { + from, + to, + kind: 'absolute', + }, }, }; } @@ -70,7 +72,7 @@ export const getSecurityTimelineRedirectUrl = ({ const encodedTimelineTimerangeParam = encode(timelineTimerangeSearchParam); const encodedTimelineFlyoutParam = encode(timelineFlyoutSearchParam); - return `${BASE_PATH}?timeline=${encodedTimelineParam}&timeRange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; + return `${baseURL}?timeline=${encodedTimelineParam}&timerange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; }; /** From 841e7b9306c676fd0959f3c1a2940080116f1f5a Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Tue, 18 Feb 2025 10:57:24 +0100 Subject: [PATCH 16/48] fix: remove unnecessary files --- .../accessors/get_alert_event_overview.tsx | 123 ------------------ 1 file changed, 123 deletions(-) delete mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx deleted file mode 100644 index 77d09357b9b9f..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_alert_event_overview.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { useMemo, useState } from 'react'; -import type { FC, PropsWithChildren } from 'react'; -import { getFieldValue } from '@kbn/discover-utils'; -import { DocViewerComponent } from '@kbn/unified-doc-viewer/src/services/types'; -import { - EuiTitle, - EuiSpacer, - EuiAccordion, - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiText, -} from '@elastic/eui'; -import * as i18n from '../translations'; -import { getEcsAllowedValueDescription, getSecurityTimelineRedirectUrl } from '../utils'; - -export const ExpandableSection: FC> = ({ - title, - children, -}) => { - const [trigger, setTrigger] = useState<'open' | 'closed'>('open'); - - const onToggle = (isOpen: boolean) => { - const newState = isOpen ? 'open' : 'closed'; - setTrigger(newState); - }; - - return ( - -

{title}

- - } - > - - - {children} - -
- ); -}; - -export const AlertEventOverview: DocViewerComponent = ({ hit }) => { - const reason = useMemo(() => getFieldValue(hit, 'kibana.alert.reason') as string, [hit]); - const description = useMemo( - () => getFieldValue(hit, 'kibana.alert.rule.description') as string, - [hit] - ); - const alertURL = useMemo(() => getFieldValue(hit, 'kibana.alert.url') as string, [hit]); - const eventKind = useMemo(() => getFieldValue(hit, 'event.kind') as string, [hit]); - const isAlert = useMemo(() => eventKind === 'signal', [eventKind]); - const eventId = useMemo(() => getFieldValue(hit, '_id') as string, [hit]); - const eventURL = useMemo( - () => - getSecurityTimelineRedirectUrl({ - from: getFieldValue(hit, '@timestamp') as string, - to: getFieldValue(hit, '@timestamp') as string, - eventId: eventId as string, - index: getFieldValue(hit, '_index') as string, - }), - [hit, eventId] - ); - - const eventCategory = useMemo(() => getFieldValue(hit, 'event.category') as string, [hit]); - - return ( - - - - {getEcsAllowedValueDescription(eventCategory)} - - - {description ? ( - - - {description} - - - ) : null} - {isAlert ? ( - - - {reason} - - - ) : null} - - - {i18n.overviewExploreButtonLabel(isAlert)} - - - - ); -}; From 12561c99a654ae96de6ff58b3d5def738d4d1086 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Tue, 18 Feb 2025 14:47:50 +0100 Subject: [PATCH 17/48] fix: tests --- .../security/config.context_awareness.ts | 5 +- .../test_suites/security/constants.ts | 2 + .../discover/context_awareness/doc_viewer.ts | 79 ++++++------- .../ftr/discover/context_awareness/index.ts | 7 +- .../context_awareness/row_indicator.ts | 106 ++++++++++++++++++ 5 files changed, 150 insertions(+), 49 deletions(-) create mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts diff --git a/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts b/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts index ab15b8d6beeb9..5ba5b45d188f5 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts @@ -15,7 +15,10 @@ export default createTestConfig({ 'Serverless Security Discover Context Awareness Functional Tests - Security Profiles', }, kbnServerArgs: [ - `--discover.experimental.enabledProfiles=${JSON.stringify(['security-root-profile'])}`, + `--discover.experimental.enabledProfiles=${JSON.stringify([ + 'security-root-profile', + 'security-document-profile', + ])}`, ], // include settings from project controller // https://github.com/elastic/elasticsearch-controller/blob/main/helm/values.yaml diff --git a/x-pack/test_serverless/functional/test_suites/security/constants.ts b/x-pack/test_serverless/functional/test_suites/security/constants.ts index 477797ca0fd95..60a0dd66b574c 100644 --- a/x-pack/test_serverless/functional/test_suites/security/constants.ts +++ b/x-pack/test_serverless/functional/test_suites/security/constants.ts @@ -7,3 +7,5 @@ export const SECURITY_ES_ARCHIVES_DIR = 'x-pack/test/security_solution_cypress/es_archives'; export const AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION = '1.13.0-preview02'; +export const SECURITY_SOLUTION_DATA_VIEW = + '.alerts-security.alerts-default,apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*,-*elastic-cloud-logs-*'; diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts index fffe09229dfa8..7f189a7559be2 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts @@ -6,9 +6,10 @@ */ import path from 'path'; +import { v4 as uuidv4 } from 'uuid'; import { encode } from '@kbn/rison'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; -import { SECURITY_ES_ARCHIVES_DIR } from '../../../constants'; +import { SECURITY_ES_ARCHIVES_DIR, SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); @@ -16,23 +17,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dataViews = getService('dataViews'); const esArchiver = getService('esArchiver'); const queryBar = getService('queryBar'); - const esClient = getService('es'); + const securitySolutionApi = getService('securitySolutionApi'); describe('security root profile', () => { before(async () => { await PageObjects.svlCommonPage.loginAsViewer(); + await esArchiver.loadIfNeeded(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); }); + after(async () => { + await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); + }); describe('doc viewer', () => { describe('events', () => { - before(async () => { - await esArchiver.loadIfNeeded(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); - }); - - after(async () => { - await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); - }); - describe('DataView mode', () => { it('should open event overview tab', async () => { await PageObjects.common.navigateToActualUrl('discover', undefined, { @@ -64,44 +61,34 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); - const expanddocviewerbutton = await testSubjects.find('doctableexpandtogglecolumn'); + const expanddocviewerbutton = await testSubjects.find('docTableExpandToggleColumn'); await expanddocviewerbutton.click(); - await testSubjects.existOrFail('eventoverview', { timeout: 2500 }); + await testSubjects.existOrFail('eventOverview', { timeout: 2500 }); }); }); }); describe('alerts', () => { before(async () => { - await PageObjects.svlCommonPage.loginAsAdmin(); - // // sleep - // try { - // const alias = await esClient.indices.get({ - // index: '.alerts-security.alerts-default', - // }); - // - // try { - // const stream = await esClient.indices.getDataStream({ - // name: '.alerts-security.alerts-default', - // }); - // } catch (err) {} - // - // await esClient.indices.deleteDataStream({ - // name: '.alerts-security.alerts-default', - // }); - - // .deleteAlias({ - // name: '.alerts-security.alerts-default', - // index: '.alerts-security.alerts-default-000001', - // }); - await esArchiver.load(path.join(SECURITY_ES_ARCHIVES_DIR, 'ransomware_detection'), { - useCreate: true, + const testRunUuid = uuidv4(); + const ruleName = `Test Rule - ${testRunUuid}`; + + await securitySolutionApi.createRule({ + body: { + name: ruleName, + description: 'test rule', + type: 'query', + enabled: true, + query: '_id: *', + index: ['auditbeat-*'], + from: 'now-10y', + interval: '1m', + severity: 'high', + risk_score: 70, + }, }); - }); - - after(async () => { - await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'query_alert')); + await PageObjects.svlCommonPage.loginAsAdmin(); }); describe('DataView mode', () => { @@ -109,11 +96,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToActualUrl('discover', undefined, { ensureCurrentUrl: false, }); - await dataViews.createFromSearchBar({ - name: '.alerts-*', - adHoc: true, - hasTimeField: true, - }); + + await queryBar.setQuery('event.kind: "signal"'); await queryBar.clickQuerySubmitButton(); await PageObjects.discover.waitUntilSearchingHasFinished(); const expandDocViewerButton = await testSubjects.find('docTableExpandToggleColumn'); @@ -124,16 +108,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('ES|QL mode', () => { it('should open alert overview tab', async () => { + const query = `FROM ${SECURITY_SOLUTION_DATA_VIEW} | WHERE event.kind == "signal"`; + const state = encode({ datasource: { type: 'esql' }, - query: { esql: 'from .alert-*' }, + query: { esql: query }, }); await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { ensureCurrentUrl: false, }); + await PageObjects.discover.waitUntilSearchingHasFinished(); - const expandDocViewerButton = await testSubjects.find('doctableexpandtogglecolumn'); + const expandDocViewerButton = await testSubjects.find('docTableExpandToggleColumn'); await expandDocViewerButton.click(); await testSubjects.existOrFail('alertOverview', { timeout: 2500 }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts index 351025b929425..7f3992eb8baf9 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import moment from 'moment'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { @@ -12,7 +13,8 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['timePicker', 'svlCommonPage']); const from = '2017-06-10T14:00:00.000Z'; - const to = '2024-06-10T16:30:00.000Z'; + // next day to include alerts generated in the tests + const to = moment().add(1, 'day').toISOString(); describe('discover/security/context_awareness', function () { this.tags(['esGate']); @@ -39,7 +41,8 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); }); - // loadTestFile(require.resolve('./cell_renderer')); + loadTestFile(require.resolve('./cell_renderer')); loadTestFile(require.resolve('./doc_viewer')); + loadTestFile(require.resolve('./row_indicator')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts new file mode 100644 index 0000000000000..ec5b71f332ec5 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import path from 'path'; +import { v4 as uuidv4 } from 'uuid'; +import { encode } from '@kbn/rison'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { SECURITY_ES_ARCHIVES_DIR, SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); + const esArchiver = getService('esArchiver'); + const queryBar = getService('queryBar'); + const securitySolutionApi = getService('securitySolutionApi'); + const find = getService('find'); + + describe('security document profile', () => { + before(async () => { + await PageObjects.svlCommonPage.loginAsViewer(); + await esArchiver.loadIfNeeded(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); + }); + + after(async () => { + await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); + }); + describe('doc viewer', () => { + describe('alerts and events', () => { + before(async () => { + const testRunUuid = uuidv4(); + const ruleName = `Test Rule - ${testRunUuid}`; + + await securitySolutionApi.createRule({ + body: { + name: ruleName, + description: 'test rule', + type: 'query', + enabled: true, + query: '_id: *', + index: ['auditbeat-*'], + from: 'now-10y', + interval: '1m', + severity: 'high', + risk_score: 70, + }, + }); + await PageObjects.svlCommonPage.loginAsAdmin(); + }); + + describe('DataView mode', () => { + it('should have row indicator for both event and alert', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + + await queryBar.clickQuerySubmitButton(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + expect( + await find.existsByCssSelector( + '[data-test-subj="unifiedDataTableRowColorIndicatorCell"][title="alert"]' + ) + ).to.eql(true); + expect( + await find.existsByCssSelector( + '[data-test-subj="unifiedDataTableRowColorIndicatorCell"][title="event"]' + ) + ).to.eql(true); + }); + + describe('ES|QL mode', () => { + it('should have row indicator for both event and alert', async () => { + const query = `FROM ${SECURITY_SOLUTION_DATA_VIEW}`; + + const state = encode({ + datasource: { type: 'esql' }, + query: { esql: query }, + }); + + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + + await PageObjects.discover.waitUntilSearchingHasFinished(); + + expect( + await find.existsByCssSelector( + '[data-test-subj="unifiedDataTableRowColorIndicatorCell"][title="alert"]' + ) + ).to.eql(true); + expect( + await find.existsByCssSelector( + '[data-test-subj="unifiedDataTableRowColorIndicatorCell"][title="event"]' + ) + ).to.eql(true); + }); + }); + }); + }); + }); + }); +} From 94fd20df7624f57ffcb2e37c2d26fdff488f1f55 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 28 Feb 2025 05:09:57 +0100 Subject: [PATCH 18/48] feat: add row action --- .../security/security_document_profile/profile.tsx | 2 +- .../security/security_root_profile/profile.tsx | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx index cad096f742841..0d19df18fd1a1 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx @@ -17,7 +17,7 @@ import * as i18n from '../translations'; export const createSecurityDocumentProfileProvider: SecurityProfileProviderFactory< DocumentProfileProvider -> = (_services: ProfileProviderServices) => { +> = (services: ProfileProviderServices) => { return { profileId: SECURITY_PROFILE_ID.document, experimental: true, diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx index 1a259f3cc51fc..5f893fbe8a853 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx @@ -16,6 +16,7 @@ import { createCellRendererAccessor } from '../accessors/get_cell_renderer_acces import { createAppWrapperAccessor } from '../accessors/create_app_wrapper_accessor'; import { getDefaultSecuritySolutionAppState } from '../accessors/get_default_app_state'; import { getAlertEventRowIndicator } from '../accessors/get_row_indicator'; +import { getRowAdditionalLeadingControls } from '../accessors/get_row_additional_control'; interface SecurityRootProfileContext { appWrapper?: FunctionComponent>; @@ -38,6 +39,14 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< profileId: 'security-root-profile', isExperimental: true, profile: { + getRowAdditionalLeadingControls: (prev) => (params) => { + const additionalControls = prev(params) || []; + + return getRowAdditionalLeadingControls({ + services, + additionalControls, + }); + }, getRenderAppWrapper: (PrevWrapper, params) => { const AppWrapper = params.context.appWrapper ?? EmptyAppWrapper; return ({ children }) => ( From fa73ec202d4a5eb41bb535b27a59a710ed604b60 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 21 Mar 2025 06:02:24 +0100 Subject: [PATCH 19/48] fix: additional files --- .../accessors/get_row_additional_control.tsx | 33 ++++++++++ .../explore_in_security.tsx | 62 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx new file mode 100644 index 0000000000000..3f248e6881c70 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { RowControlColumn } from '@kbn/discover-utils'; +import { ProfileProviderServices } from '../../profile_provider_services'; +import { ExploreInSecurity } from '../components/row_leading_controls/explore_in_security'; + +export function getRowAdditionalLeadingControls({ + services, + additionalControls, +}: { + services: ProfileProviderServices; + additionalControls: RowControlColumn[]; +}) { + const createExploreInSecurityControl = (): RowControlColumn => { + return { + id: 'exploreInSecurity', + headerAriaLabel: 'Explore in Security', + renderControl: (Control, props) => ( + + ), + }; + }; + + return [...additionalControls, createExploreInSecurityControl()]; +} diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx new file mode 100644 index 0000000000000..7814d273b0d2e --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { useCallback, useMemo } from 'react'; +import { RowControlComponent, RowControlRowProps, getFieldValue } from '@kbn/discover-utils'; +import { ProfileProviderServices } from '../../../profile_provider_services'; +import { getSecurityTimelineRedirectUrl } from '../../utils'; + +export interface ExploreInSecurityProps { + Control: RowControlComponent; + rowProps: RowControlRowProps; + services: ProfileProviderServices; +} + +export function ExploreInSecurity({ Control, rowProps, services }: ExploreInSecurityProps) { + const hit = rowProps.record; + const { + application: { getUrlForApp }, + } = services; + + const timelinesURL = getUrlForApp('securitySolutionUI', { + path: 'alerts', + }); + + const alertURL = useMemo(() => getFieldValue(hit, 'kibana.alert.url') as string, [hit]); + const eventKind = useMemo(() => getFieldValue(hit, 'event.kind') as string, [hit]); + const isAlert = useMemo(() => eventKind === 'signal', [eventKind]); + const eventId = useMemo(() => getFieldValue(hit, '_id') as string, [hit]); + const eventURL = useMemo( + () => + getSecurityTimelineRedirectUrl({ + from: getFieldValue(hit, '@timestamp') as string, + to: getFieldValue(hit, '@timestamp') as string, + eventId: eventId as string, + index: getFieldValue(hit, '_index') as string, + baseURL: timelinesURL, + }), + [hit, eventId, timelinesURL] + ); + + const url = useMemo(() => (isAlert ? alertURL : eventURL), [isAlert, alertURL, eventURL]); + + const onControlClick = useCallback(() => { + window.open(url, '_blank'); + }, [url]); + + const label = useMemo(() => `Explore ${isAlert ? 'alert' : 'event'} in Security`, [isAlert]); + return ( + + ); +} From 7d9a63906e6961f1aa514e5899e34545d887018b Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 21 Mar 2025 06:20:30 +0100 Subject: [PATCH 20/48] fix: types + remove unnecessary files --- .../register_profile_providers.ts | 3 + .../accessors/alert_event_overview.test.tsx | 142 ------------------ .../accessors/alert_event_overview.tsx | 133 ---------------- .../security_document_profile/profile.tsx | 57 ------- .../security_root_profile/profile.tsx | 7 +- 5 files changed, 7 insertions(+), 335 deletions(-) delete mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx delete mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx delete mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts index ffeab1853f390..7dfa961c1a0ab 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -29,8 +29,11 @@ import { createObservabilityRootProfileProvider } from './observability/observab import { createTracesDataSourceProfileProvider } from './observability/traces_data_source_profile'; import { createDeprecationLogsDataSourceProfileProvider } from './common/deprecation_logs'; import { createClassicNavRootProfileProvider } from './common/classic_nav_root_profile'; +<<<<<<< HEAD import { createObservabilityTracesSpanDocumentProfileProvider } from './observability/traces_document_profile/span_document_profile'; import { createObservabilityTracesTransactionDocumentProfileProvider } from './observability/traces_document_profile/transaction_document_profile'; +======= +>>>>>>> 05dce5419e7 (fix: types + remove unnecessary files) /** * Register profile providers for root, data source, and document contexts to the profile profile services diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx deleted file mode 100644 index f1b08a12599e0..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.test.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; -import { AlertEventOverview } from './alert_event_overview'; -import { DataTableRecord } from '@kbn/discover-utils'; -import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { EcsFlat } from '@elastic/ecs'; -import { useDiscoverServices } from '../../../../hooks/use_discover_services'; -import { encode } from '@kbn/rison'; - -jest.mock('../../../../hooks/use_discover_services'); - -const TEST_TIMELINE_URL = 'test-timeline-url'; - -const mockGetUrlForApp = jest.fn().mockReturnValue(TEST_TIMELINE_URL); - -const mockDiscoverServices = { - application: { - getUrlForApp: mockGetUrlForApp, - }, -}; - -const mockRow = { - 'kibana.alert.reason': 'test-reason', - 'kibana.alert.rule.description': 'test-description', - 'event.kind': 'signal', - _id: 'test-id', - '@timestamp': '2021-08-02T14:00:00.000Z', - 'kibana.alert.url': 'test-url', -}; - -const mockHit = { - flattened: mockRow, -} as unknown as DataTableRecord; - -const mockDataView = dataViewMock; - -describe('AlertEventOverviewAccessor', () => { - beforeEach(() => { - (useDiscoverServices as jest.Mock).mockReturnValue(mockDiscoverServices); - }); - describe('expandable sections', () => { - test('should return the expandable sections correctly', () => { - render(); - expect(screen.getByTestId('expandableHeader-About')).toBeVisible(); - expect(screen.getByTestId('expandableContent-About')).toBeVisible(); - - fireEvent.click(screen.getByTestId('expandableHeader-About')); - expect(screen.getByTestId('expandableContent-About')).not.toBeVisible(); - }); - - test('should show expected sections', () => { - render(); - expect(screen.getByTestId('expandableHeader-About')).toBeVisible(); - - expect(screen.getByTestId('expandableHeader-Description')).toBeVisible(); - expect(screen.getByTestId('expandableContent-Description')).toHaveTextContent( - 'test-description' - ); - - expect(screen.getByTestId('expandableHeader-Reason')).toBeVisible(); - expect(screen.getByTestId('expandableContent-Reason')).toHaveTextContent('test-reason'); - - expect(screen.getByTestId('exploreSecurity')).toBeVisible(); - - expect(screen.getByTestId('exploreSecurity').getAttribute('href')).toBe('test-url'); - }); - }); - - describe('data', () => { - test('should return Ecs description for different event types correctly', () => { - const localMockHit = { - flattened: { - ...mockRow, - 'event.category': 'process', - }, - } as unknown as DataTableRecord; - - render(); - - expect(screen.getByTestId('expandableContent-About')).toHaveTextContent( - EcsFlat['event.category'].allowed_values.find((i) => i.name === 'process') - ?.description as string - ); - }); - - test('should display timeline redirect url correctly', () => { - const localMockHit = { - flattened: { - ...mockRow, - 'event.kind': 'event', - 'event.category': 'process', - }, - } as unknown as DataTableRecord; - render(); - const expectedURLJSON = { - timeline: { - activeTab: 'query', - isOpen: true, - query: { - expression: '_id: test-id', - kind: 'kuery', - }, - }, - - timeRange: { - timeline: { - timerange: { - from: mockRow['@timestamp'], - to: mockRow['@timestamp'], - kind: 'absolute', - }, - }, - }, - - timelineFlyout: { - right: { - id: 'document-details-right', - params: { - id: 'test-id', - scopeId: 'timeline-1', - }, - }, - }, - }; - - expect(screen.getByTestId('exploreSecurity').getAttribute('href')).toBe( - `test-timeline-url?timeline=${encode(expectedURLJSON.timeline)}&timerange=${encode( - expectedURLJSON.timeRange - )}&timelineFlyout=${encode(expectedURLJSON.timelineFlyout)}` - ); - }); - }); -}); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx deleted file mode 100644 index 99cc63c1236c4..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/alert_event_overview.tsx +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { useMemo, useState } from 'react'; -import type { FC, PropsWithChildren } from 'react'; -import { getFieldValue } from '@kbn/discover-utils'; -import { DocViewerComponent } from '@kbn/unified-doc-viewer/src/services/types'; -import { - EuiTitle, - EuiSpacer, - EuiAccordion, - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiText, -} from '@elastic/eui'; -import * as i18n from '../translations'; -import { getEcsAllowedValueDescription, getSecurityTimelineRedirectUrl } from '../utils'; -import { useDiscoverServices } from '../../../../hooks/use_discover_services'; - -export const ExpandableSection: FC> = ({ - title, - children, -}) => { - const [trigger, setTrigger] = useState<'open' | 'closed'>('open'); - - const onToggle = (isOpen: boolean) => { - const newState = isOpen ? 'open' : 'closed'; - setTrigger(newState); - }; - - return ( - -

{title}

- - } - > - - - {children} - -
- ); -}; - -export const AlertEventOverview: DocViewerComponent = ({ hit }) => { - const { - application: { getUrlForApp }, - } = useDiscoverServices(); - - const timelinesURL = getUrlForApp('securitySolutionUI', { - path: 'alerts', - }); - - const reason = useMemo(() => getFieldValue(hit, 'kibana.alert.reason') as string, [hit]); - const description = useMemo( - () => getFieldValue(hit, 'kibana.alert.rule.description') as string, - [hit] - ); - const alertURL = useMemo(() => getFieldValue(hit, 'kibana.alert.url') as string, [hit]); - const eventKind = useMemo(() => getFieldValue(hit, 'event.kind') as string, [hit]); - const isAlert = useMemo(() => eventKind === 'signal', [eventKind]); - const eventId = useMemo(() => getFieldValue(hit, '_id') as string, [hit]); - const eventURL = useMemo( - () => - getSecurityTimelineRedirectUrl({ - from: getFieldValue(hit, '@timestamp') as string, - to: getFieldValue(hit, '@timestamp') as string, - eventId: eventId as string, - index: getFieldValue(hit, '_index') as string, - baseURL: timelinesURL, - }), - [hit, eventId, timelinesURL] - ); - - const eventCategory = useMemo(() => getFieldValue(hit, 'event.category') as string, [hit]); - - return ( - - - - {getEcsAllowedValueDescription(eventCategory)} - - - {description ? ( - - - {description} - - - ) : null} - {isAlert ? ( - - - {reason} - - - ) : null} - - - {i18n.overviewExploreButtonLabel(isAlert)} - - - - ); -}; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx deleted file mode 100644 index 0d19df18fd1a1..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { getFieldValue } from '@kbn/discover-utils'; -import { DocumentProfileProvider, DocumentType, SolutionType } from '../../../profiles'; -import { ProfileProviderServices } from '../../profile_provider_services'; -import { SecurityProfileProviderFactory } from '../types'; -import { SECURITY_PROFILE_ID } from '../constants'; -import { AlertEventOverview } from '../accessors/alert_event_overview'; -import * as i18n from '../translations'; - -export const createSecurityDocumentProfileProvider: SecurityProfileProviderFactory< - DocumentProfileProvider -> = (services: ProfileProviderServices) => { - return { - profileId: SECURITY_PROFILE_ID.document, - experimental: true, - profile: { - getDocViewer: (prev) => (params) => { - const prevDocViewer = prev(params); - const isAlert = getFieldValue(params.record, 'event.kind') === 'signal'; - - return { - ...prevDocViewer, - docViewsRegistry: (registry) => { - registry.add({ - id: 'doc_view_alerts_overview', - title: i18n.overviewTabTitle(isAlert), - order: 0, - component: AlertEventOverview, - }); - - return prevDocViewer.docViewsRegistry(registry); - }, - }; - }, - }, - resolve: ({ rootContext }) => { - if (rootContext.solutionType !== SolutionType.Security) { - return { isMatch: false }; - } - - return { - isMatch: true, - context: { - type: DocumentType.Default, - }, - }; - }, - }; -}; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx index 5f893fbe8a853..7053d751becb5 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx @@ -20,7 +20,7 @@ import { getRowAdditionalLeadingControls } from '../accessors/get_row_additional interface SecurityRootProfileContext { appWrapper?: FunctionComponent>; - getCellRenderer?: ( + getSecuritySolutionCellRenderer?: ( fieldName: string ) => FunctionComponent | undefined; } @@ -66,7 +66,8 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< 'destination.ip', 'kibana.alert.workflow_status', ].forEach((fieldName) => { - entries[fieldName] = context.getCellRenderer?.(fieldName) ?? entries[fieldName]; + entries[fieldName] = + context.getSecuritySolutionCellRenderer?.(fieldName) ?? entries[fieldName]; }); return entries; }, @@ -88,7 +89,7 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< context: { solutionType: SolutionType.Security, appWrapper: getAppWrapper?.(), - getCellRenderer, + getSecuritySolutionCellRenderer: getCellRenderer, }, }; }, From 61edf6ae1c350c3b8f67f98ee1879fece0732f1b Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 21 Mar 2025 06:56:49 +0100 Subject: [PATCH 21/48] tests --- .../explore_in_security.test.tsx | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx new file mode 100644 index 0000000000000..cade01f5138bf --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { RowControlComponent } from '@kbn/discover-utils'; +import { RowControlRowProps } from '@kbn/discover-utils'; +import { ExploreInSecurity } from './explore_in_security'; +import { EuiButton } from '@elastic/eui'; +import { ProfileProviderServices } from '../../../profile_provider_services'; + +const TEST_TIMELINE_URL = 'test-timeline-url'; + +const mockGetUrlForApp = jest.fn().mockReturnValue(TEST_TIMELINE_URL); + +const mockDiscoverServices = { + application: { + getUrlForApp: mockGetUrlForApp, + }, +} as unknown as ProfileProviderServices; + +const mockRow = { + 'kibana.alert.reason': 'test-reason', + 'kibana.alert.rule.description': 'test-description', + 'event.kind': 'signal', + _id: 'test-id', + '@timestamp': '2021-08-02T14:00:00.000Z', + 'kibana.alert.url': 'test-url', +}; + +const mockRowProps = { + record: { + flattened: mockRow, + }, +} as unknown as RowControlRowProps; + +const mockWindowOpen = jest.fn(); + +jest.spyOn(window, 'open').mockImplementation(mockWindowOpen); + +const MockControl: RowControlComponent = ({ iconType, onClick, label }) => { + const onButtonClick = () => { + onClick?.({} as RowControlRowProps); + }; + return ( + + {label} + + ); +}; + +describe('Explore In Security Control', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + test('should return the alert control correctly', () => { + render( + + ); + + expect(screen.getByText('Explore alert in Security')).toBeVisible(); + fireEvent.click(screen.getByText('Explore alert in Security')); + expect(mockWindowOpen).toHaveBeenCalledWith('test-url', '_blank'); + }); + + test('should return the event control correctly', () => { + const mockEventRowProps = { + ...mockRowProps, + record: { + flattened: { + ...mockRow, + 'event.kind': 'event', + }, + }, + } as unknown as RowControlRowProps; + + const expectedEventURL = `${TEST_TIMELINE_URL}?timeline=(activeTab:query,isOpen:!t,query:(expression:'_id: ${mockRow._id}',kind:kuery))&timerange=(timeline:(timerange:(from:'${mockRow['@timestamp']}',kind:absolute,to:'${mockRow['@timestamp']}')))&timelineFlyout=(right:(id:document-details-right,params:(id:${mockRow._id},scopeId:timeline-1)))`; + + render( + + ); + + expect(screen.getByText('Explore event in Security')).toBeVisible(); + fireEvent.click(screen.getByText('Explore event in Security')); + expect(mockWindowOpen).toHaveBeenCalledWith(expectedEventURL, '_blank'); + }); +}); From 6423ec3f709260c58ab8112f2bbcf3b3bfe0e8f5 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 21 Mar 2025 07:25:10 +0100 Subject: [PATCH 22/48] more tests housekeeping --- .../components/open_timeline/helpers.test.ts | 10 ++ .../security/config.context_awareness.ts | 5 +- .../context_awareness/cell_renderer.ts | 15 +- .../context_awareness/default_state.ts | 6 + .../discover/context_awareness/doc_viewer.ts | 133 ------------------ .../ftr/discover/context_awareness/index.ts | 3 +- .../row_additional_leading_controls.ts | 6 + 7 files changed, 37 insertions(+), 141 deletions(-) create mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts create mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_additional_leading_controls.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 0d5e013e30380..1faf3acff3461 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -729,6 +729,10 @@ describe('helpers', () => { timelineType: TimelineTypeEnum.default, onOpenTimeline, openTimeline: true, + query: { + kind: 'kuery', + expression: 'foo: bar', + }, }; (resolveTimeline as jest.Mock).mockResolvedValue(untitledTimeline); renderHook(async () => { @@ -742,6 +746,12 @@ describe('helpers', () => { id: TimelineId.active, timeline: expect.objectContaining({ columns: defaultUdtHeaders, + kqlQuery: { + filterQuery: { + serializedQuery: 'foo: bar', + kuery: { expression: 'foo: bar', kind: 'kuery' }, + }, + }, }), }) ); diff --git a/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts b/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts index 5ba5b45d188f5..ab15b8d6beeb9 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts @@ -15,10 +15,7 @@ export default createTestConfig({ 'Serverless Security Discover Context Awareness Functional Tests - Security Profiles', }, kbnServerArgs: [ - `--discover.experimental.enabledProfiles=${JSON.stringify([ - 'security-root-profile', - 'security-document-profile', - ])}`, + `--discover.experimental.enabledProfiles=${JSON.stringify(['security-root-profile'])}`, ], // include settings from project controller // https://github.com/elastic/elasticsearch-controller/blob/main/helm/values.yaml diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts index 19d0020f73cba..19cb7259c0ba0 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts @@ -18,6 +18,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const queryBar = getService('queryBar'); + const defaultColumns = [ + '@timestamp', + 'kibana.alert.workflow_status', + 'message', + 'event.category', + 'event.action', + 'host.name', + 'source.ip', + 'destination.ip', + 'user.name', + ]; + describe('security root profile', () => { before(async () => { await PageObjects.svlCommonPage.loginAsViewer(); @@ -43,9 +55,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await queryBar.setQuery('host.name: "siem-kibana"'); await queryBar.clickQuerySubmitButton(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.dragFieldToTable('host.name'); expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be( - '@timestamp, host.name' + defaultColumns.join(', ') ); // security host.name button const hostName = await testSubjects.findAll('host-details-button', 2500); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts deleted file mode 100644 index 7f189a7559be2..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/doc_viewer.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import path from 'path'; -import { v4 as uuidv4 } from 'uuid'; -import { encode } from '@kbn/rison'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; -import { SECURITY_ES_ARCHIVES_DIR, SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); - const testSubjects = getService('testSubjects'); - const dataViews = getService('dataViews'); - const esArchiver = getService('esArchiver'); - const queryBar = getService('queryBar'); - const securitySolutionApi = getService('securitySolutionApi'); - - describe('security root profile', () => { - before(async () => { - await PageObjects.svlCommonPage.loginAsViewer(); - await esArchiver.loadIfNeeded(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); - }); - - after(async () => { - await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); - }); - describe('doc viewer', () => { - describe('events', () => { - describe('DataView mode', () => { - it('should open event overview tab', async () => { - await PageObjects.common.navigateToActualUrl('discover', undefined, { - ensureCurrentUrl: false, - }); - await dataViews.createFromSearchBar({ - name: 'auditbeat-2022', - adHoc: true, - hasTimeField: true, - }); - await queryBar.setQuery('host.name: "siem-kibana"'); - await queryBar.clickQuerySubmitButton(); - await PageObjects.discover.waitUntilSearchingHasFinished(); - const expandDocViewerButton = await testSubjects.find('docTableExpandToggleColumn'); - await expandDocViewerButton.click(); - - await testSubjects.existOrFail('eventOverview', { timeout: 2500 }); - }); - }); - - describe('ES|QL mode', () => { - it('should open event overview tab', async () => { - const state = encode({ - datasource: { type: 'esql' }, - query: { esql: 'from auditbeat-2022 | where host.name == "siem-kibana"' }, - }); - - await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { - ensureCurrentUrl: false, - }); - await PageObjects.discover.waitUntilSearchingHasFinished(); - const expanddocviewerbutton = await testSubjects.find('docTableExpandToggleColumn'); - await expanddocviewerbutton.click(); - - await testSubjects.existOrFail('eventOverview', { timeout: 2500 }); - }); - }); - }); - - describe('alerts', () => { - before(async () => { - const testRunUuid = uuidv4(); - const ruleName = `Test Rule - ${testRunUuid}`; - - await securitySolutionApi.createRule({ - body: { - name: ruleName, - description: 'test rule', - type: 'query', - enabled: true, - query: '_id: *', - index: ['auditbeat-*'], - from: 'now-10y', - interval: '1m', - severity: 'high', - risk_score: 70, - }, - }); - await PageObjects.svlCommonPage.loginAsAdmin(); - }); - - describe('DataView mode', () => { - it('should open alert overview tab', async () => { - await PageObjects.common.navigateToActualUrl('discover', undefined, { - ensureCurrentUrl: false, - }); - - await queryBar.setQuery('event.kind: "signal"'); - await queryBar.clickQuerySubmitButton(); - await PageObjects.discover.waitUntilSearchingHasFinished(); - const expandDocViewerButton = await testSubjects.find('docTableExpandToggleColumn'); - await expandDocViewerButton.click(); - - await testSubjects.existOrFail('alertOverview', { timeout: 2500 }); - }); - - describe('ES|QL mode', () => { - it('should open alert overview tab', async () => { - const query = `FROM ${SECURITY_SOLUTION_DATA_VIEW} | WHERE event.kind == "signal"`; - - const state = encode({ - datasource: { type: 'esql' }, - query: { esql: query }, - }); - - await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { - ensureCurrentUrl: false, - }); - - await PageObjects.discover.waitUntilSearchingHasFinished(); - const expandDocViewerButton = await testSubjects.find('docTableExpandToggleColumn'); - await expandDocViewerButton.click(); - - await testSubjects.existOrFail('alertOverview', { timeout: 2500 }); - }); - }); - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts index 7f3992eb8baf9..ef0ebfa5effb9 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts @@ -42,7 +42,6 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid }); loadTestFile(require.resolve('./cell_renderer')); - loadTestFile(require.resolve('./doc_viewer')); - loadTestFile(require.resolve('./row_indicator')); + // loadTestFile(require.resolve('./row_indicator')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_additional_leading_controls.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_additional_leading_controls.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_additional_leading_controls.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ From 7cb5f175f16fc96ec27aaf10fd4997af795ca062 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 21 Mar 2025 11:12:22 +0100 Subject: [PATCH 23/48] test: ftr context awareness --- .../register_profile_providers.ts | 3 - .../context_awareness/cell_renderer.ts | 104 ++++++------------ .../context_awareness/default_state.ts | 57 ++++++++++ .../ftr/discover/context_awareness/index.ts | 31 +++++- .../row_additional_leading_controls.ts | 6 - .../context_awareness/row_indicator.ts | 73 ++++-------- .../context_awareness/row_leading_controls.ts | 73 ++++++++++++ 7 files changed, 219 insertions(+), 128 deletions(-) delete mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_additional_leading_controls.ts create mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_leading_controls.ts diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts index 7dfa961c1a0ab..ffeab1853f390 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -29,11 +29,8 @@ import { createObservabilityRootProfileProvider } from './observability/observab import { createTracesDataSourceProfileProvider } from './observability/traces_data_source_profile'; import { createDeprecationLogsDataSourceProfileProvider } from './common/deprecation_logs'; import { createClassicNavRootProfileProvider } from './common/classic_nav_root_profile'; -<<<<<<< HEAD import { createObservabilityTracesSpanDocumentProfileProvider } from './observability/traces_document_profile/span_document_profile'; import { createObservabilityTracesTransactionDocumentProfileProvider } from './observability/traces_document_profile/transaction_document_profile'; -======= ->>>>>>> 05dce5419e7 (fix: types + remove unnecessary files) /** * Register profile providers for root, data source, and document contexts to the profile profile services diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts index 19cb7259c0ba0..9fd3377ac3493 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts @@ -7,89 +7,55 @@ import kbnRison from '@kbn/rison'; import expect from '@kbn/expect'; -import path from 'path'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; -import { SECURITY_ES_ARCHIVES_DIR } from '../../../constants'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); const testSubjects = getService('testSubjects'); - const dataViews = getService('dataViews'); - const esArchiver = getService('esArchiver'); const queryBar = getService('queryBar'); - const defaultColumns = [ - '@timestamp', - 'kibana.alert.workflow_status', - 'message', - 'event.category', - 'event.action', - 'host.name', - 'source.ip', - 'destination.ip', - 'user.name', - ]; - - describe('security root profile', () => { + describe('cell renderer', () => { before(async () => { - await PageObjects.svlCommonPage.loginAsViewer(); - await esArchiver.loadIfNeeded(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); - }); - - after(async () => { - await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); + await PageObjects.svlCommonPage.loginAsAdmin(); }); - describe('cell renderers', () => { - describe('host.name', () => { - describe('DataView mode', () => { - it('should open host.name flyout', async () => { - await PageObjects.common.navigateToActualUrl('discover', undefined, { - ensureCurrentUrl: false, - }); - await dataViews.createFromSearchBar({ - name: 'auditbeat-2022', - adHoc: true, - hasTimeField: true, - }); - await queryBar.setQuery('host.name: "siem-kibana"'); - await queryBar.clickQuerySubmitButton(); - await PageObjects.discover.waitUntilSearchingHasFinished(); - expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be( - defaultColumns.join(', ') - ); - // security host.name button - const hostName = await testSubjects.findAll('host-details-button', 2500); - expect(hostName).to.have.length(1); - await hostName[0].click(); - await testSubjects.existOrFail('host-panel-header', { timeout: 2500 }); - await testSubjects.existOrFail('asset-criticality-selector', { timeout: 2500 }); - await testSubjects.existOrFail('observedEntity-accordion', { timeout: 2500 }); + describe('DataView mode', () => { + describe('cell renderer', () => { + it('should open host.name flyout with correct content', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, }); + await queryBar.setQuery('host.name: "siem-kibana" AND event.kind: "signal"'); + await queryBar.clickQuerySubmitButton(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const hostName = await testSubjects.findAll('host-details-button', 2500); + expect(hostName).to.have.length(1); + await hostName[0].click(); + await testSubjects.existOrFail('host-panel-header', { timeout: 2500 }); + await testSubjects.existOrFail('asset-criticality-selector', { timeout: 2500 }); + await testSubjects.existOrFail('observedEntity-accordion', { timeout: 2500 }); }); + }); - describe('ES|QL mode', () => { - it('should open host.name flyout', async () => { - const state = kbnRison.encode({ - dataSource: { type: 'esql' }, - - query: { esql: 'from auditbeat-2022 | WHERE host.name == "siem-kibana"' }, - }); - - await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { - ensureCurrentUrl: false, - }); - await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.dragFieldToTable('host.name'); - expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be('host.name'); - // security host.name button - const hostName = await testSubjects.findAll('host-details-button', 2500); - expect(hostName).to.have.length(1); - await hostName[0].click(); - await testSubjects.existOrFail('host-panel-header', { timeout: 2500 }); - await testSubjects.existOrFail('asset-criticality-selector', { timeout: 2500 }); - await testSubjects.existOrFail('observedEntity-accordion', { timeout: 2500 }); + describe('ES|QL mode', () => { + it('should open host.name flyout', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: 'from auditbeat-2022 | WHERE host.name == "siem-kibana" and event.kind != "signal"', + }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.discover.dragFieldToTable('host.name'); + const hostName = await testSubjects.findAll('host-details-button', 2500); + expect(hostName).to.have.length(1); + await hostName[0].click(); + await testSubjects.existOrFail('host-panel-header', { timeout: 2500 }); + await testSubjects.existOrFail('asset-criticality-selector', { timeout: 2500 }); + await testSubjects.existOrFail('observedEntity-accordion', { timeout: 2500 }); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts index 1fec1c76430eb..84790ed750d23 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts @@ -4,3 +4,60 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +import kbnRison from '@kbn/rison'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +const defaultEventColumns = [ + '@timestamp', + 'kibana.alert.workflow_status', + 'message', + 'event.category', + 'event.action', + 'host.name', + 'source.ip', + 'destination.ip', + 'user.name', +]; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); + const queryBar = getService('queryBar'); + + describe('row leading controls', () => { + before(async () => { + await PageObjects.svlCommonPage.loginAsAdmin(); + }); + + describe('DataView mode', () => { + it('should have correct list of columns', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await queryBar.setQuery('host.name: "siem-kibana"'); + await queryBar.clickQuerySubmitButton(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be( + defaultEventColumns.join(', ') + ); + }); + }); + + describe('ES|QL mode', () => { + it('should have correct list of columns', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + }); + + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be( + defaultEventColumns.join(', ') + ); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts index ef0ebfa5effb9..95153fe83a490 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts @@ -6,12 +6,17 @@ */ import moment from 'moment'; +import { v4 as uuidv4 } from 'uuid'; +import path from 'path'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { SECURITY_ES_ARCHIVES_DIR } from '../../../constants'; export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['timePicker', 'svlCommonPage']); + const securitySolutionApi = getService('securitySolutionApi'); + const from = '2017-06-10T14:00:00.000Z'; // next day to include alerts generated in the tests const to = moment().add(1, 'day').toISOString(); @@ -29,6 +34,25 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid await kibanaServer.uiSettings.update({ 'timepicker:timeDefaults': `{ "from": "${from}", "to": "${to}"}`, }); + await esArchiver.loadIfNeeded(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); + + const testRunUuid = uuidv4(); + const ruleName = `Test Rule - ${testRunUuid}`; + + await securitySolutionApi.createRule({ + body: { + name: ruleName, + description: 'test rule', + type: 'query', + enabled: true, + query: '_id: *', + index: ['auditbeat-*'], + from: 'now-10y', + interval: '1m', + severity: 'high', + risk_score: 70, + }, + }); }); after(async () => { @@ -38,10 +62,15 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid await kibanaServer.importExport.unload( 'src/platform/test/functional/fixtures/kbn_archiver/discover/context_awareness' ); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + + await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); }); + loadTestFile(require.resolve('./default_state')); loadTestFile(require.resolve('./cell_renderer')); - // loadTestFile(require.resolve('./row_indicator')); + loadTestFile(require.resolve('./row_indicator')); + loadTestFile(require.resolve('./row_leading_controls')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_additional_leading_controls.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_additional_leading_controls.ts deleted file mode 100644 index 1fec1c76430eb..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_additional_leading_controls.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts index ec5b71f332ec5..dca62b49403b0 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts @@ -6,48 +6,23 @@ */ import expect from '@kbn/expect'; -import path from 'path'; -import { v4 as uuidv4 } from 'uuid'; import { encode } from '@kbn/rison'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; -import { SECURITY_ES_ARCHIVES_DIR, SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; +import { SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); - const esArchiver = getService('esArchiver'); const queryBar = getService('queryBar'); - const securitySolutionApi = getService('securitySolutionApi'); const find = getService('find'); describe('security document profile', () => { before(async () => { await PageObjects.svlCommonPage.loginAsViewer(); - await esArchiver.loadIfNeeded(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); }); - after(async () => { - await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); - }); - describe('doc viewer', () => { + describe('row indicators', () => { describe('alerts and events', () => { before(async () => { - const testRunUuid = uuidv4(); - const ruleName = `Test Rule - ${testRunUuid}`; - - await securitySolutionApi.createRule({ - body: { - name: ruleName, - description: 'test rule', - type: 'query', - enabled: true, - query: '_id: *', - index: ['auditbeat-*'], - from: 'now-10y', - interval: '1m', - severity: 'high', - risk_score: 70, - }, - }); await PageObjects.svlCommonPage.loginAsAdmin(); }); @@ -71,33 +46,33 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ) ).to.eql(true); }); + }); - describe('ES|QL mode', () => { - it('should have row indicator for both event and alert', async () => { - const query = `FROM ${SECURITY_SOLUTION_DATA_VIEW}`; + describe('ES|QL mode', () => { + it('should have row indicator for both event and alert', async () => { + const query = `FROM ${SECURITY_SOLUTION_DATA_VIEW}`; - const state = encode({ - datasource: { type: 'esql' }, - query: { esql: query }, - }); + const state = encode({ + datasource: { type: 'esql' }, + query: { esql: query }, + }); - await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { - ensureCurrentUrl: false, - }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); - await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); - expect( - await find.existsByCssSelector( - '[data-test-subj="unifiedDataTableRowColorIndicatorCell"][title="alert"]' - ) - ).to.eql(true); - expect( - await find.existsByCssSelector( - '[data-test-subj="unifiedDataTableRowColorIndicatorCell"][title="event"]' - ) - ).to.eql(true); - }); + expect( + await find.existsByCssSelector( + '[data-test-subj="unifiedDataTableRowColorIndicatorCell"][title="alert"]' + ) + ).to.eql(true); + expect( + await find.existsByCssSelector( + '[data-test-subj="unifiedDataTableRowColorIndicatorCell"][title="event"]' + ) + ).to.eql(true); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_leading_controls.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_leading_controls.ts new file mode 100644 index 0000000000000..e061c3074adcb --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_leading_controls.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import kbnRison from '@kbn/rison'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); + const testSubjects = getService('testSubjects'); + const queryBar = getService('queryBar'); + + describe('row leading controls', () => { + before(async () => { + await PageObjects.svlCommonPage.loginAsAdmin(); + }); + + describe('DataView mode', () => { + it('should have explore event and alert in security leading action', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await queryBar.setQuery('host.name: "siem-kibana"'); + await queryBar.clickQuerySubmitButton(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const exploreInSecurityAction = await testSubjects.findAll( + 'unifiedDataTable_rowControl_additionalRowControl_exploreInSecurity', + 2500 + ); + expect(exploreInSecurityAction).to.have.length(2); + + expect(await exploreInSecurityAction[0].getAttribute('aria-label')).to.eql( + 'Explore alert in Security' + ); + expect(await exploreInSecurityAction[1].getAttribute('aria-label')).to.eql( + 'Explore event in Security' + ); + }); + }); + + describe('ES|QL mode', () => { + it('should have explore event in security leading action', async () => { + const query = `FROM ${SECURITY_SOLUTION_DATA_VIEW}`; + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: query }, + }); + + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const exploreInSecurityAction = await testSubjects.findAll( + 'unifiedDataTable_rowControl_additionalRowControl_exploreInSecurity', + 2500 + ); + expect(exploreInSecurityAction).to.have.length(2); + + expect(await exploreInSecurityAction[0].getAttribute('aria-label')).to.eql( + 'Explore alert in Security' + ); + expect(await exploreInSecurityAction[1].getAttribute('aria-label')).to.eql( + 'Explore event in Security' + ); + }); + }); + }); +} From 145b2b50d9eef823b6c460d75e3b5ca45ca49cd7 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 21 Mar 2025 11:42:13 +0100 Subject: [PATCH 24/48] translations for label --- .../explore_in_security.tsx | 8 +++++--- .../profile_providers/security/translations.ts | 17 ++--------------- .../profile_providers/security/utils/index.tsx | 16 +--------------- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx index 7814d273b0d2e..d92e2a90518ca 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx @@ -8,9 +8,11 @@ */ import React, { useCallback, useMemo } from 'react'; -import { RowControlComponent, RowControlRowProps, getFieldValue } from '@kbn/discover-utils'; -import { ProfileProviderServices } from '../../../profile_provider_services'; +import type { RowControlComponent, RowControlRowProps } from '@kbn/discover-utils'; +import { getFieldValue } from '@kbn/discover-utils'; +import type { ProfileProviderServices } from '../../../profile_provider_services'; import { getSecurityTimelineRedirectUrl } from '../../utils'; +import { exploreRowActionLabel } from '../../translations'; export interface ExploreInSecurityProps { Control: RowControlComponent; @@ -50,7 +52,7 @@ export function ExploreInSecurity({ Control, rowProps, services }: ExploreInSecu window.open(url, '_blank'); }, [url]); - const label = useMemo(() => `Explore ${isAlert ? 'alert' : 'event'} in Security`, [isAlert]); + const label = useMemo(() => exploreRowActionLabel(isAlert), [isAlert]); return ( - i18n.translate('discover.profile.security.flyout.overviewTabTitle', { - values: { isAlert }, - defaultMessage: '{isAlert, select, true {Alerts Overview} other {Event Overview}}', - }); - -export const overviewExploreButtonLabel = (isAlert: boolean) => - i18n.translate('discover.profile.security.flyout.overviewExploreButtonLabel', { +export const exploreRowActionLabel = (isAlert: boolean) => + i18n.translate('discover.profile.security.rowAction.exploreButtonLabel', { values: { isAlert }, defaultMessage: 'Explore {isAlert, select, true {Alert} other {Event}} in Security', }); - -export const noEcsDescriptionReason = i18n.translate( - 'discover.profile.security.flyout.noEventKindDescriptionMessage', - { - defaultMessage: "This field doesn't have a description because it's not part of ECS.", - } -); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx index e6c1bc254ca5c..4a5e1d9b3d956 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -8,8 +8,7 @@ */ import { encode } from '@kbn/rison'; -import { EcsFlat } from '@elastic/ecs'; -import * as i18n from '../translations'; +import type { EcsFlat } from '@elastic/ecs'; export interface CustomQuery { kind: 'kuery' | 'lucene'; @@ -74,16 +73,3 @@ export const getSecurityTimelineRedirectUrl = ({ return `${baseURL}?timeline=${encodedTimelineParam}&timerange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; }; - -/** - * Helper function to return the description of an allowed value of the specified field - * @param fieldName - * @param value - * @returns ecs description of the value - */ -export const getEcsAllowedValueDescription = (value: string): string => { - const allowedValues: EcsAllowedValue[] = EcsFlat['event.category']?.allowed_values ?? []; - const result = - allowedValues?.find((item) => item.name === value)?.description ?? i18n.noEcsDescriptionReason; - return result; -}; From c87521739c32c5a55b63cd2de75274d7e8faf4bc Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 21 Mar 2025 11:07:46 +0000 Subject: [PATCH 25/48] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../security/accessors/get_default_app_state.ts | 2 +- .../accessors/get_row_additional_control.tsx | 4 ++-- .../security/accessors/get_row_indicator.test.ts | 4 ++-- .../security/accessors/get_row_indicator.ts | 2 +- .../explore_in_security.test.tsx | 6 +++--- .../security/security_root_profile/profile.tsx | 12 +++++++----- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts index 16b5d4c05a18e..b53be1751234b 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { DefaultAppStateExtension } from '../../../types'; +import type { DefaultAppStateExtension } from '../../../types'; export const getDefaultSecuritySolutionAppState: () => DefaultAppStateExtension = () => ({ breakdownField: 'kibana.alert.workflow_status', diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx index 3f248e6881c70..7150a9b55f68b 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx @@ -8,8 +8,8 @@ */ import React from 'react'; -import { RowControlColumn } from '@kbn/discover-utils'; -import { ProfileProviderServices } from '../../profile_provider_services'; +import type { RowControlColumn } from '@kbn/discover-utils'; +import type { ProfileProviderServices } from '../../profile_provider_services'; import { ExploreInSecurity } from '../components/row_leading_controls/explore_in_security'; export function getRowAdditionalLeadingControls({ diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts index d34e7efd63d42..e4f1f60a2f0e7 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts @@ -7,9 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { DataTableRecord } from '@kbn/discover-utils'; +import type { DataTableRecord } from '@kbn/discover-utils'; import { getAlertEventRowIndicator } from './get_row_indicator'; -import { EuiThemeComputed } from '@elastic/eui'; +import type { EuiThemeComputed } from '@elastic/eui'; describe('getAlertEventRowIndicator', () => { it('should return the correct color and label for an event row', () => { diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts index b8040842bb75d..e716da8b9533d 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts @@ -8,7 +8,7 @@ */ import { getFieldValue } from '@kbn/discover-utils'; -import { UnifiedDataTableProps } from '@kbn/unified-data-table'; +import type { UnifiedDataTableProps } from '@kbn/unified-data-table'; export const getAlertEventRowIndicator: NonNullable = ( row, diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx index cade01f5138bf..67782d3f1ce50 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx @@ -9,11 +9,11 @@ import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { RowControlComponent } from '@kbn/discover-utils'; -import { RowControlRowProps } from '@kbn/discover-utils'; +import type { RowControlComponent } from '@kbn/discover-utils'; +import type { RowControlRowProps } from '@kbn/discover-utils'; import { ExploreInSecurity } from './explore_in_security'; import { EuiButton } from '@elastic/eui'; -import { ProfileProviderServices } from '../../../profile_provider_services'; +import type { ProfileProviderServices } from '../../../profile_provider_services'; const TEST_TIMELINE_URL = 'test-timeline-url'; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx index 7053d751becb5..ae117f087d73d 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx @@ -7,11 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { FunctionComponent, PropsWithChildren } from 'react'; -import { DataGridCellValueElementProps } from '@kbn/unified-data-table'; -import { RootProfileProvider, SolutionType } from '../../../profiles'; -import { ProfileProviderServices } from '../../profile_provider_services'; -import { SecurityProfileProviderFactory } from '../types'; +import type { FunctionComponent, PropsWithChildren } from 'react'; +import React from 'react'; +import type { DataGridCellValueElementProps } from '@kbn/unified-data-table'; +import type { RootProfileProvider } from '../../../profiles'; +import { SolutionType } from '../../../profiles'; +import type { ProfileProviderServices } from '../../profile_provider_services'; +import type { SecurityProfileProviderFactory } from '../types'; import { createCellRendererAccessor } from '../accessors/get_cell_renderer_accessor'; import { createAppWrapperAccessor } from '../accessors/create_app_wrapper_accessor'; import { getDefaultSecuritySolutionAppState } from '../accessors/get_default_app_state'; From d580cc7660d826e9bf750c8317039174c15baead Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 28 Mar 2025 14:19:06 +0100 Subject: [PATCH 26/48] fix: add link button --- .../custom_control_columns/types.ts | 3 +- .../row_control_column.test.tsx | 72 +++++++++++++++++++ .../row_control_column.tsx | 26 +++++-- .../explore_in_security.test.tsx | 30 +++----- .../explore_in_security.tsx | 10 +-- 5 files changed, 110 insertions(+), 31 deletions(-) diff --git a/src/platform/packages/shared/kbn-discover-utils/src/components/custom_control_columns/types.ts b/src/platform/packages/shared/kbn-discover-utils/src/components/custom_control_columns/types.ts index 6d7d11a6c7d30..4f9a113661ebc 100644 --- a/src/platform/packages/shared/kbn-discover-utils/src/components/custom_control_columns/types.ts +++ b/src/platform/packages/shared/kbn-discover-utils/src/components/custom_control_columns/types.ts @@ -24,8 +24,9 @@ export interface RowControlProps { disabled?: boolean; iconType: IconType; label: string; - onClick: ((props: RowControlRowProps) => void) | undefined; + onClick?: (props: RowControlRowProps) => void; tooltipContent?: React.ReactNode; + href?: string; } export type RowControlComponent = FC; diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx index c8f3a4602f147..4b6bcced705a4 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx @@ -90,4 +90,76 @@ describe('getRowControlColumn', () => { expect(screen.getByText('Control tooltip text!')).toBeInTheDocument(); }); }); + + it('should render control button as a link when href is passed', () => { + const props = { + id: 'test_row_control', + headerAriaLabel: 'row control', + renderControl: jest.fn((Control, rowProps) => ( + + )), + }; + const rowControlColumn = getRowControlColumn(props); + const RowControlColumn = + rowControlColumn.rowCellRender as React.FC; + render( + + + + ); + const link = screen.getByTestId('unifiedDataTable_rowControl_test_row_control'); + expect(link).toBeInTheDocument(); + expect(link).toBeInstanceOf(HTMLAnchorElement); + expect(link).toHaveAttribute('href', 'https://www.elastic.co'); + }); + + it('should render control button as a disabled event when href is passed but the disabled == true', () => { + const props = { + id: 'test_row_control', + headerAriaLabel: 'row control', + renderControl: jest.fn((Control, rowProps) => ( + + )), + }; + const rowControlColumn = getRowControlColumn(props); + const RowControlColumn = + rowControlColumn.rowCellRender as React.FC; + render( + + + + ); + const link = screen.getByTestId('unifiedDataTable_rowControl_test_row_control'); + expect(link).toBeInTheDocument(); + expect(link).toBeInstanceOf(HTMLButtonElement); + expect(link).not.toHaveAttribute('href', 'https://www.elastic.co'); + expect(link).toHaveAttribute('disabled'); + }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx index 8ab1158ad55d9..bf580de782506 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx @@ -16,6 +16,7 @@ import { EuiToolTip, } from '@elastic/eui'; import { RowControlColumn, RowControlProps } from '@kbn/discover-utils'; +import { getRouterLinkProps } from '@kbn/router-utils'; import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants'; import { useControlColumn } from '../../../hooks/use_control_column'; @@ -35,27 +36,38 @@ export const RowControlCell = ({ disabled, iconType, label, - onClick, + onClick: onClickProps, tooltipContent, + href: hrefProps, ...extraProps }) => { const classNameProp = Boolean(tooltipContent) ? {} : { className: 'unifiedDataTable__rowControl' }; + const { href, onClick } = getRouterLinkProps({ + /** + * if the control is disabled, we ignore the href prop and button should be disabled. + * Additionaly, display as a button instead of an anchor + */ + href: disabled ? undefined : hrefProps, + onClick: (_ev: React.MouseEvent) => { + if (record && onClickProps) { + onClickProps?.({ record, rowIndex }); + } + }, + }); + const control = ( { - if (record && onClick) { - onClick({ record, rowIndex }); - } - }} + onClick={onClick} + /** if href prop is present, disabled prop is invalid and not expected since it is a link */ + {...(href ? { href } : { disabled })} {...classNameProp} {...extraProps} /> diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx index 67782d3f1ce50..462ebb11bc8ae 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx @@ -18,10 +18,12 @@ import type { ProfileProviderServices } from '../../../profile_provider_services const TEST_TIMELINE_URL = 'test-timeline-url'; const mockGetUrlForApp = jest.fn().mockReturnValue(TEST_TIMELINE_URL); +const mockNavigateToUrl = jest.fn(); const mockDiscoverServices = { application: { getUrlForApp: mockGetUrlForApp, + navigateToUrl: mockNavigateToUrl, }, } as unknown as ProfileProviderServices; @@ -40,19 +42,9 @@ const mockRowProps = { }, } as unknown as RowControlRowProps; -const mockWindowOpen = jest.fn(); - -jest.spyOn(window, 'open').mockImplementation(mockWindowOpen); - -const MockControl: RowControlComponent = ({ iconType, onClick, label }) => { - const onButtonClick = () => { - onClick?.({} as RowControlRowProps); - }; - return ( - - {label} - - ); +const MockControl: RowControlComponent = ({ label, ...props }) => { + // @ts-expect-error + return {label}; }; describe('Explore In Security Control', () => { @@ -68,9 +60,9 @@ describe('Explore In Security Control', () => { /> ); - expect(screen.getByText('Explore alert in Security')).toBeVisible(); - fireEvent.click(screen.getByText('Explore alert in Security')); - expect(mockWindowOpen).toHaveBeenCalledWith('test-url', '_blank'); + expect(screen.getByText('Explore Alert in Security')).toBeVisible(); + fireEvent.click(screen.getByTestId('explore-in-security')); + expect(mockNavigateToUrl).toHaveBeenCalled(); }); test('should return the event control correctly', () => { @@ -94,8 +86,8 @@ describe('Explore In Security Control', () => { /> ); - expect(screen.getByText('Explore event in Security')).toBeVisible(); - fireEvent.click(screen.getByText('Explore event in Security')); - expect(mockWindowOpen).toHaveBeenCalledWith(expectedEventURL, '_blank'); + expect(screen.getByText('Explore Event in Security')).toBeVisible(); + screen.debug(undefined, 1000000); + expect(screen.getByTestId('explore-in-security')).toHaveAttribute('href', expectedEventURL); }); }); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx index d92e2a90518ca..8a84756dfe3ea 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx @@ -23,7 +23,7 @@ export interface ExploreInSecurityProps { export function ExploreInSecurity({ Control, rowProps, services }: ExploreInSecurityProps) { const hit = rowProps.record; const { - application: { getUrlForApp }, + application: { getUrlForApp, navigateToUrl }, } = services; const timelinesURL = getUrlForApp('securitySolutionUI', { @@ -49,16 +49,18 @@ export function ExploreInSecurity({ Control, rowProps, services }: ExploreInSecu const url = useMemo(() => (isAlert ? alertURL : eventURL), [isAlert, alertURL, eventURL]); const onControlClick = useCallback(() => { - window.open(url, '_blank'); - }, [url]); + navigateToUrl(url); + }, [url, navigateToUrl]); const label = useMemo(() => exploreRowActionLabel(isAlert), [isAlert]); return ( ); } From 81f9617c20a4c26efa5f68195f307b45aa030ea3 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 28 Mar 2025 13:33:16 +0000 Subject: [PATCH 27/48] [CI] Auto-commit changed files from 'node scripts/yarn_deduplicate' --- .../packages/shared/kbn-unified-data-table/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/packages/shared/kbn-unified-data-table/tsconfig.json b/src/platform/packages/shared/kbn-unified-data-table/tsconfig.json index db4ea94b7e28f..2066969a30567 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/tsconfig.json +++ b/src/platform/packages/shared/kbn-unified-data-table/tsconfig.json @@ -46,5 +46,6 @@ "@kbn/data-grid-in-table-search", "@kbn/data-view-utils", "@kbn/react-hooks", + "@kbn/router-utils", ] } From aac8a4ff06ea4d49ae96041ddc059f735a08a4e1 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 28 Mar 2025 15:17:44 +0100 Subject: [PATCH 28/48] update codeowners --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ee14f2f7cc90f..984f4af53441f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2290,6 +2290,9 @@ x-pack/solutions/security/plugins/security_solution/public/asset_inventory @elas /x-pack/solutions/security/plugins/security_solution/public/siem_migrations @elastic/security-threat-hunting /x-pack/solutions/security/plugins/security_solution/public/common/components/control_columns @elastic/security-threat-hunting +/x-pack/test_serverless/functional/test_suites/security/ftr/discover @elastic/security-threat-hunting +x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts @elastic/security-threat-hunting + ## Security Solution Threat Hunting areas - Threat Hunting Investigations /x-pack/solutions/security/plugins/security_solution/common/api/timeline @elastic/security-threat-hunting-investigations From 035df6e6acbe839cf1d2d7a1a058c4d4c6ac3a88 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 28 Mar 2025 17:11:33 +0100 Subject: [PATCH 29/48] fix: jest tests --- .../security/accessors/get_row_indicator.test.ts | 8 ++++++-- .../security_solution/public/app/home/index.test.tsx | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts index e4f1f60a2f0e7..d9bdfd5341ed4 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.test.ts @@ -14,7 +14,9 @@ import type { EuiThemeComputed } from '@elastic/eui'; describe('getAlertEventRowIndicator', () => { it('should return the correct color and label for an event row', () => { const row = { - 'event.kind': 'event', + flattened: { + 'event.kind': 'event', + }, } as unknown as DataTableRecord; const euiTheme = { @@ -33,7 +35,9 @@ describe('getAlertEventRowIndicator', () => { it('should return the correct color and label for an alert row', () => { const row = { - 'event.kind': 'signal', + flattened: { + 'event.kind': 'signal', + }, } as unknown as DataTableRecord; const euiTheme = { diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/home/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/app/home/index.test.tsx index c34f63cfebbbd..aa69a2a15d687 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/home/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/app/home/index.test.tsx @@ -643,6 +643,10 @@ describe('HomePage', () => { activeTab: 'query', graphEventId: '', isOpen: false, + query: { + expression: '', + kind: 'kuery', + }, }); }); From 48a35219d5de256cc02f0a800c53dd568adbfbbf Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 28 Mar 2025 19:04:31 +0100 Subject: [PATCH 30/48] fix: FTR test --- .../explore_in_security.test.tsx | 11 +++++--- .../explore_in_security.tsx | 1 - .../ftr/discover/context_awareness/index.ts | 6 ++--- .../context_awareness/row_leading_controls.ts | 25 +++++++++++++------ 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx index 462ebb11bc8ae..5914e900d363c 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx @@ -42,9 +42,13 @@ const mockRowProps = { }, } as unknown as RowControlRowProps; -const MockControl: RowControlComponent = ({ label, ...props }) => { - // @ts-expect-error - return {label}; +const MockControl: RowControlComponent = ({ label, tooltipContent: _, ...props }) => { + return ( + // @ts-expect-error + + {label} + + ); }; describe('Explore In Security Control', () => { @@ -87,7 +91,6 @@ describe('Explore In Security Control', () => { ); expect(screen.getByText('Explore Event in Security')).toBeVisible(); - screen.debug(undefined, 1000000); expect(screen.getByTestId('explore-in-security')).toHaveAttribute('href', expectedEventURL); }); }); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx index 8a84756dfe3ea..021e5acb3be49 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx @@ -55,7 +55,6 @@ export function ExploreInSecurity({ Control, rowProps, services }: ExploreInSecu const label = useMemo(() => exploreRowActionLabel(isAlert), [isAlert]); return ( Date: Mon, 31 Mar 2025 15:14:34 +0200 Subject: [PATCH 31/48] fix: cypress --- .../cypress/e2e/explore/urls/state.cy.ts | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts index b5189b846225f..923e88c1a4558 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts @@ -235,7 +235,10 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { .should('have.attr', 'href') .and( 'contain', - `/app/security/network?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))` + "/app/security/network?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + + "&query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')" + + "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))" ); }); it('sets KQL in host page and detail page and check if href match on breadcrumb, tabs and subTabs', () => { @@ -249,13 +252,19 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { .should('have.attr', 'href') .and( 'contain', - `/app/security/hosts?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))` + "/app/security/hosts?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + + "&query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')" + + "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); cy.get(NETWORK) .should('have.attr', 'href') .and( 'contain', - `/app/security/network?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))` + "/app/security/network?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + + "&query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')" + + "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); toggleNavigationPanel(EXPLORE_PANEL_BTN); cy.get(HOSTS_NAMES).first().should('have.text', 'siem-kibana'); @@ -268,7 +277,9 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { .should('have.attr', 'href') .and( 'contain', - "/app/security/hosts/name/siem-kibana/anomalies?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" + "/app/security/hosts/name/siem-kibana/anomalies?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + + "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); cy.get(BREADCRUMBS) @@ -276,14 +287,20 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { .should('have.attr', 'href') .and( 'contain', - `/app/security/hosts?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))` + "/app/security/hosts?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + + "&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" + + "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); cy.get(BREADCRUMBS) .eq(3) .should('have.attr', 'href') .and( 'contain', - `/app/security/hosts/name/siem-kibana?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))` + "/app/security/hosts/name/siem-kibana?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + + "&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" + + "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); }); From 9741ed18e55a5d34c9f89eb99612be4f4f62bd5e Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 20 Jun 2025 15:06:17 +0200 Subject: [PATCH 32/48] fix: remove row action + add flyout overview tab --- .../custom_control_columns/types.ts | 9 +- .../row_control_column.test.tsx | 87 +---------- .../row_control_column.tsx | 66 ++------- .../kbn-unified-data-table/tsconfig.json | 1 - .../register_profile_providers.ts | 2 + .../accessors/get_row_additional_control.tsx | 33 ----- .../components/alert_event_overview.test.tsx | 140 ++++++++++++++++++ .../components/alert_event_overview.tsx | 133 +++++++++++++++++ .../explore_in_security.test.tsx | 96 ------------ .../explore_in_security.tsx | 65 -------- .../index.ts} | 9 +- .../security_document_profile/profile.tsx | 58 ++++++++ .../security_root_profile/profile.tsx | 32 +--- .../security/translations.ts | 19 +++ .../security/utils/index.tsx | 16 +- .../security_solution/public/plugin.tsx | 31 +--- 16 files changed, 393 insertions(+), 404 deletions(-) delete mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx delete mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx delete mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx rename src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/{accessors/create_app_wrapper_accessor.ts => security_document_profile/index.ts} (61%) create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx diff --git a/src/platform/packages/shared/kbn-discover-utils/src/components/custom_control_columns/types.ts b/src/platform/packages/shared/kbn-discover-utils/src/components/custom_control_columns/types.ts index 4f9a113661ebc..80c9029825829 100644 --- a/src/platform/packages/shared/kbn-discover-utils/src/components/custom_control_columns/types.ts +++ b/src/platform/packages/shared/kbn-discover-utils/src/components/custom_control_columns/types.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { EuiButtonIconProps, EuiDataGridControlColumn, IconType } from '@elastic/eui'; +import { EuiButtonIconProps, IconType } from '@elastic/eui'; import type { Interpolation, Theme } from '@emotion/react'; import React, { FC, ReactElement } from 'react'; import { DataTableRecord } from '../../types'; @@ -24,16 +24,13 @@ export interface RowControlProps { disabled?: boolean; iconType: IconType; label: string; - onClick?: (props: RowControlRowProps) => void; + onClick: ((props: RowControlRowProps) => void) | undefined; tooltipContent?: React.ReactNode; - href?: string; } export type RowControlComponent = FC; export interface RowControlColumn { id: string; - headerAriaLabel: string; - headerCellRender?: EuiDataGridControlColumn['headerCellRender']; - renderControl: (Control: RowControlComponent, props: RowControlRowProps) => ReactElement; + render: (Control: RowControlComponent, props: RowControlRowProps) => ReactElement; } diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx index 4b6bcced705a4..e413246fab426 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx @@ -8,7 +8,6 @@ */ import React from 'react'; -import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { getRowControlColumn } from './row_control_column'; @@ -24,14 +23,11 @@ describe('getRowControlColumn', () => { const mockClick = jest.fn(); const props = { id: 'test_row_control', - headerAriaLabel: 'row control', - renderControl: jest.fn((Control, rowProps) => ( + render: jest.fn((Control, rowProps) => ( )), }; - const rowControlColumn = getRowControlColumn(props); - const RowControlColumn = - rowControlColumn.rowCellRender as React.FC; + const RowControlColumn = getRowControlColumn(props); render( { it('should wrap the Control button with a tooltip when tooltipContent is passed', async () => { const props = { id: 'test_row_control', - headerAriaLabel: 'row control', - renderControl: jest.fn((Control, rowProps) => ( + render: jest.fn((Control, rowProps) => ( { /> )), }; - const rowControlColumn = getRowControlColumn(props); - const RowControlColumn = - rowControlColumn.rowCellRender as React.FC; + const RowControlColumn = getRowControlColumn(props); render( { expect(screen.getByText('Control tooltip text!')).toBeInTheDocument(); }); }); - - it('should render control button as a link when href is passed', () => { - const props = { - id: 'test_row_control', - headerAriaLabel: 'row control', - renderControl: jest.fn((Control, rowProps) => ( - - )), - }; - const rowControlColumn = getRowControlColumn(props); - const RowControlColumn = - rowControlColumn.rowCellRender as React.FC; - render( - - - - ); - const link = screen.getByTestId('unifiedDataTable_rowControl_test_row_control'); - expect(link).toBeInTheDocument(); - expect(link).toBeInstanceOf(HTMLAnchorElement); - expect(link).toHaveAttribute('href', 'https://www.elastic.co'); - }); - - it('should render control button as a disabled event when href is passed but the disabled == true', () => { - const props = { - id: 'test_row_control', - headerAriaLabel: 'row control', - renderControl: jest.fn((Control, rowProps) => ( - - )), - }; - const rowControlColumn = getRowControlColumn(props); - const RowControlColumn = - rowControlColumn.rowCellRender as React.FC; - render( - - - - ); - const link = screen.getByTestId('unifiedDataTable_rowControl_test_row_control'); - expect(link).toBeInTheDocument(); - expect(link).toBeInstanceOf(HTMLButtonElement); - expect(link).not.toHaveAttribute('href', 'https://www.elastic.co'); - expect(link).toHaveAttribute('disabled'); - }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx index bf580de782506..cb1282c906b40 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx @@ -8,23 +8,15 @@ */ import React, { useMemo } from 'react'; -import { - EuiButtonIcon, - EuiDataGridCellValueElementProps, - EuiDataGridControlColumn, - EuiScreenReaderOnly, - EuiToolTip, -} from '@elastic/eui'; +import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; import { RowControlColumn, RowControlProps } from '@kbn/discover-utils'; -import { getRouterLinkProps } from '@kbn/router-utils'; -import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants'; import { useControlColumn } from '../../../hooks/use_control_column'; export const RowControlCell = ({ - renderControl, + rowControlColumn, ...props }: EuiDataGridCellValueElementProps & { - renderControl: RowControlColumn['renderControl']; + rowControlColumn: RowControlColumn; }) => { const { record, rowIndex } = useControlColumn(props); @@ -36,38 +28,27 @@ export const RowControlCell = ({ disabled, iconType, label, - onClick: onClickProps, + onClick, tooltipContent, - href: hrefProps, ...extraProps }) => { const classNameProp = Boolean(tooltipContent) ? {} : { className: 'unifiedDataTable__rowControl' }; - const { href, onClick } = getRouterLinkProps({ - /** - * if the control is disabled, we ignore the href prop and button should be disabled. - * Additionaly, display as a button instead of an anchor - */ - href: disabled ? undefined : hrefProps, - onClick: (_ev: React.MouseEvent) => { - if (record && onClickProps) { - onClickProps?.({ record, rowIndex }); - } - }, - }); - const control = ( { + if (record && onClick) { + onClick({ record, rowIndex }); + } + }} {...classNameProp} {...extraProps} /> @@ -87,29 +68,14 @@ export const RowControlCell = ({ return control; }, - [props.columnId, record, rowIndex] + [rowControlColumn.id, record, rowIndex] ); - return record ? renderControl(Control, { record, rowIndex }) : null; + return record ? rowControlColumn.render(Control, { record, rowIndex }) : null; }; -export const getRowControlColumn = ( - rowControlColumn: RowControlColumn -): EuiDataGridControlColumn => { - const { id, headerAriaLabel, headerCellRender, renderControl } = rowControlColumn; - - return { - id: `additionalRowControl_${id}`, - width: DEFAULT_CONTROL_COLUMN_WIDTH, - headerCellRender: - headerCellRender ?? - (() => ( - - {headerAriaLabel} - - )), - rowCellRender: (props) => { - return ; - }, +export const getRowControlColumn = (rowControlColumn: RowControlColumn) => { + return (props: EuiDataGridCellValueElementProps) => { + return ; }; }; diff --git a/src/platform/packages/shared/kbn-unified-data-table/tsconfig.json b/src/platform/packages/shared/kbn-unified-data-table/tsconfig.json index 2066969a30567..db4ea94b7e28f 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/tsconfig.json +++ b/src/platform/packages/shared/kbn-unified-data-table/tsconfig.json @@ -46,6 +46,5 @@ "@kbn/data-grid-in-table-search", "@kbn/data-view-utils", "@kbn/react-hooks", - "@kbn/router-utils", ] } diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts index 465b4c2ba1c37..c34ddb94084b9 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -31,6 +31,7 @@ import { createDeprecationLogsDataSourceProfileProvider } from './common/depreca import { createClassicNavRootProfileProvider } from './common/classic_nav_root_profile'; import { createObservabilityTracesSpanDocumentProfileProvider } from './observability/traces_document_profile/span_document_profile'; import { createObservabilityTracesTransactionDocumentProfileProvider } from './observability/traces_document_profile/transaction_document_profile'; +import { createSecurityDocumentProfileProvider } from './security/security_document_profile'; /** * Register profile providers for root, data source, and document contexts to the profile profile services @@ -149,6 +150,7 @@ const createDataSourceProfileProviders = (providerServices: ProfileProviderServi */ const createDocumentProfileProviders = (providerServices: ProfileProviderServices) => [ createExampleDocumentProfileProvider(), + createSecurityDocumentProfileProvider(providerServices), createObservabilityLogDocumentProfileProvider(providerServices), createObservabilityTracesSpanDocumentProfileProvider(providerServices), createObservabilityTracesTransactionDocumentProfileProvider(), diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx deleted file mode 100644 index 7150a9b55f68b..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_additional_control.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React from 'react'; -import type { RowControlColumn } from '@kbn/discover-utils'; -import type { ProfileProviderServices } from '../../profile_provider_services'; -import { ExploreInSecurity } from '../components/row_leading_controls/explore_in_security'; - -export function getRowAdditionalLeadingControls({ - services, - additionalControls, -}: { - services: ProfileProviderServices; - additionalControls: RowControlColumn[]; -}) { - const createExploreInSecurityControl = (): RowControlColumn => { - return { - id: 'exploreInSecurity', - headerAriaLabel: 'Explore in Security', - renderControl: (Control, props) => ( - - ), - }; - }; - - return [...additionalControls, createExploreInSecurityControl()]; -} diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx new file mode 100644 index 0000000000000..2b417700dd006 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { AlertEventOverview } from './alert_event_overview'; +import type { DataTableRecord } from '@kbn/discover-utils'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { EcsFlat } from '@elastic/ecs'; +import { useDiscoverServices } from '../../../../hooks/use_discover_services'; +import { encode } from '@kbn/rison'; + +jest.mock('../../../../hooks/use_discover_services'); + +const TEST_TIMELINE_URL = 'test-timeline-url'; + +const mockGetUrlForApp = jest.fn().mockReturnValue(TEST_TIMELINE_URL); + +const mockDiscoverServices = { + application: { + getUrlForApp: mockGetUrlForApp, + }, +}; + +const mockRow = { + 'kibana.alert.reason': 'test-reason', + 'kibana.alert.rule.description': 'test-description', + 'event.kind': 'signal', + _id: 'test-id', + '@timestamp': '2021-08-02T14:00:00.000Z', + 'kibana.alert.url': 'test-url', +}; + +const mockHit = { + flattened: mockRow, +} as unknown as DataTableRecord; + +const mockDataView = dataViewMock; + +describe('AlertEventOverviewAccessor', () => { + beforeEach(() => { + (useDiscoverServices as jest.Mock).mockReturnValue(mockDiscoverServices); + }); + describe('expandable sections', () => { + test('should return the expandable sections correctly', () => { + render(); + expect(screen.getByTestId('expandableHeader-About')).toBeVisible(); + expect(screen.getByTestId('expandableContent-About')).toBeVisible(); + + fireEvent.click(screen.getByTestId('expandableHeader-About')); + expect(screen.getByTestId('expandableContent-About')).not.toBeVisible(); + }); + + test('should show expected sections', () => { + render(); + expect(screen.getByTestId('expandableHeader-About')).toBeVisible(); + + expect(screen.getByTestId('expandableHeader-Description')).toBeVisible(); + expect(screen.getByTestId('expandableContent-Description')).toHaveTextContent( + 'test-description' + ); + + expect(screen.getByTestId('expandableHeader-Reason')).toBeVisible(); + expect(screen.getByTestId('expandableContent-Reason')).toHaveTextContent('test-reason'); + + expect(screen.getByTestId('exploreSecurity')).toBeVisible(); + + expect(screen.getByTestId('exploreSecurity').getAttribute('href')).toBe('test-url'); + }); + }); + + describe('data', () => { + test('should return Ecs description for different event types correctly', () => { + const localMockHit = { + flattened: { + ...mockRow, + 'event.category': 'process', + }, + } as unknown as DataTableRecord; + + render(); + + expect(screen.getByTestId('expandableContent-About')).toHaveTextContent( + EcsFlat['event.category'].allowed_values.find((i) => i.name === 'process') + ?.description as string + ); + }); + + test('should display timeline redirect url correctly', () => { + const localMockHit = { + flattened: { + ...mockRow, + 'event.kind': 'event', + 'event.category': 'process', + }, + } as unknown as DataTableRecord; + render(); + const expectedURLJSON = { + timeline: { + activeTab: 'query', + isOpen: true, + query: { + expression: '_id: test-id', + kind: 'kuery', + }, + }, + + timeRange: { + timeline: { + from: mockRow['@timestamp'], + to: mockRow['@timestamp'], + linkTo: false, + }, + }, + + timelineFlyout: { + right: { + id: 'document-details-right', + params: { + id: 'test-id', + scopeId: 'timeline-1', + }, + }, + }, + }; + + expect(screen.getByTestId('exploreSecurity').getAttribute('href')).toBe( + `test-timeline-url?timeline=${encode(expectedURLJSON.timeline)}&timeRange=${encode( + expectedURLJSON.timeRange + )}&timelineFlyout=${encode(expectedURLJSON.timelineFlyout)}` + ); + }); + }); +}); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx new file mode 100644 index 0000000000000..137d630685bb4 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { useMemo, useState } from 'react'; +import type { FC, PropsWithChildren } from 'react'; +import { getFieldValue } from '@kbn/discover-utils'; +import type { DocViewerComponent } from '@kbn/unified-doc-viewer/src/services/types'; +import { + EuiTitle, + EuiSpacer, + EuiAccordion, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiText, +} from '@elastic/eui'; +import * as i18n from '../translations'; +import { getEcsAllowedValueDescription, getSecurityTimelineRedirectUrl } from '../utils'; +import { useDiscoverServices } from '../../../../hooks/use_discover_services'; + +export const ExpandableSection: FC> = ({ + title, + children, +}) => { + const [trigger, setTrigger] = useState<'open' | 'closed'>('open'); + + const onToggle = (isOpen: boolean) => { + const newState = isOpen ? 'open' : 'closed'; + setTrigger(newState); + }; + + return ( + +

{title}

+ + } + > + + + {children} + +
+ ); +}; + +export const AlertEventOverview: DocViewerComponent = ({ hit }) => { + const { + application: { getUrlForApp }, + } = useDiscoverServices(); + + const timelinesURL = getUrlForApp('securitySolutionUI', { + path: 'alerts', + }); + + const reason = useMemo(() => getFieldValue(hit, 'kibana.alert.reason') as string, [hit]); + const description = useMemo( + () => getFieldValue(hit, 'kibana.alert.rule.description') as string, + [hit] + ); + const alertURL = useMemo(() => getFieldValue(hit, 'kibana.alert.url') as string, [hit]); + const eventKind = useMemo(() => getFieldValue(hit, 'event.kind') as string, [hit]); + const isAlert = useMemo(() => eventKind === 'signal', [eventKind]); + const eventId = useMemo(() => getFieldValue(hit, '_id') as string, [hit]); + const eventURL = useMemo( + () => + getSecurityTimelineRedirectUrl({ + from: getFieldValue(hit, '@timestamp') as string, + to: getFieldValue(hit, '@timestamp') as string, + eventId: eventId as string, + index: getFieldValue(hit, '_index') as string, + baseURL: timelinesURL, + }), + [hit, eventId, timelinesURL] + ); + + const eventCategory = useMemo(() => getFieldValue(hit, 'event.category') as string, [hit]); + + return ( + + + + {getEcsAllowedValueDescription(eventCategory)} + + + {description ? ( + + + {description} + + + ) : null} + {isAlert ? ( + + + {reason} + + + ) : null} + + + {i18n.overviewExploreButtonLabel(isAlert)} + + + + ); +}; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx deleted file mode 100644 index 5914e900d363c..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; -import type { RowControlComponent } from '@kbn/discover-utils'; -import type { RowControlRowProps } from '@kbn/discover-utils'; -import { ExploreInSecurity } from './explore_in_security'; -import { EuiButton } from '@elastic/eui'; -import type { ProfileProviderServices } from '../../../profile_provider_services'; - -const TEST_TIMELINE_URL = 'test-timeline-url'; - -const mockGetUrlForApp = jest.fn().mockReturnValue(TEST_TIMELINE_URL); -const mockNavigateToUrl = jest.fn(); - -const mockDiscoverServices = { - application: { - getUrlForApp: mockGetUrlForApp, - navigateToUrl: mockNavigateToUrl, - }, -} as unknown as ProfileProviderServices; - -const mockRow = { - 'kibana.alert.reason': 'test-reason', - 'kibana.alert.rule.description': 'test-description', - 'event.kind': 'signal', - _id: 'test-id', - '@timestamp': '2021-08-02T14:00:00.000Z', - 'kibana.alert.url': 'test-url', -}; - -const mockRowProps = { - record: { - flattened: mockRow, - }, -} as unknown as RowControlRowProps; - -const MockControl: RowControlComponent = ({ label, tooltipContent: _, ...props }) => { - return ( - // @ts-expect-error - - {label} - - ); -}; - -describe('Explore In Security Control', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - test('should return the alert control correctly', () => { - render( - - ); - - expect(screen.getByText('Explore Alert in Security')).toBeVisible(); - fireEvent.click(screen.getByTestId('explore-in-security')); - expect(mockNavigateToUrl).toHaveBeenCalled(); - }); - - test('should return the event control correctly', () => { - const mockEventRowProps = { - ...mockRowProps, - record: { - flattened: { - ...mockRow, - 'event.kind': 'event', - }, - }, - } as unknown as RowControlRowProps; - - const expectedEventURL = `${TEST_TIMELINE_URL}?timeline=(activeTab:query,isOpen:!t,query:(expression:'_id: ${mockRow._id}',kind:kuery))&timerange=(timeline:(timerange:(from:'${mockRow['@timestamp']}',kind:absolute,to:'${mockRow['@timestamp']}')))&timelineFlyout=(right:(id:document-details-right,params:(id:${mockRow._id},scopeId:timeline-1)))`; - - render( - - ); - - expect(screen.getByText('Explore Event in Security')).toBeVisible(); - expect(screen.getByTestId('explore-in-security')).toHaveAttribute('href', expectedEventURL); - }); -}); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx deleted file mode 100644 index 021e5acb3be49..0000000000000 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/row_leading_controls/explore_in_security.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { useCallback, useMemo } from 'react'; -import type { RowControlComponent, RowControlRowProps } from '@kbn/discover-utils'; -import { getFieldValue } from '@kbn/discover-utils'; -import type { ProfileProviderServices } from '../../../profile_provider_services'; -import { getSecurityTimelineRedirectUrl } from '../../utils'; -import { exploreRowActionLabel } from '../../translations'; - -export interface ExploreInSecurityProps { - Control: RowControlComponent; - rowProps: RowControlRowProps; - services: ProfileProviderServices; -} - -export function ExploreInSecurity({ Control, rowProps, services }: ExploreInSecurityProps) { - const hit = rowProps.record; - const { - application: { getUrlForApp, navigateToUrl }, - } = services; - - const timelinesURL = getUrlForApp('securitySolutionUI', { - path: 'alerts', - }); - - const alertURL = useMemo(() => getFieldValue(hit, 'kibana.alert.url') as string, [hit]); - const eventKind = useMemo(() => getFieldValue(hit, 'event.kind') as string, [hit]); - const isAlert = useMemo(() => eventKind === 'signal', [eventKind]); - const eventId = useMemo(() => getFieldValue(hit, '_id') as string, [hit]); - const eventURL = useMemo( - () => - getSecurityTimelineRedirectUrl({ - from: getFieldValue(hit, '@timestamp') as string, - to: getFieldValue(hit, '@timestamp') as string, - eventId: eventId as string, - index: getFieldValue(hit, '_index') as string, - baseURL: timelinesURL, - }), - [hit, eventId, timelinesURL] - ); - - const url = useMemo(() => (isAlert ? alertURL : eventURL), [isAlert, alertURL, eventURL]); - - const onControlClick = useCallback(() => { - navigateToUrl(url); - }, [url, navigateToUrl]); - - const label = useMemo(() => exploreRowActionLabel(isAlert), [isAlert]); - return ( - - ); -} diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/create_app_wrapper_accessor.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/index.ts similarity index 61% rename from src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/create_app_wrapper_accessor.ts rename to src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/index.ts index 42382f088b7a3..5c5d9d20fb662 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/create_app_wrapper_accessor.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/index.ts @@ -7,11 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { SecuritySolutionAppWrapperFeature } from '@kbn/discover-shared-plugin/public'; - -export const createAppWrapperAccessor = async ( - appWrapperFeature?: SecuritySolutionAppWrapperFeature -) => { - if (!appWrapperFeature) return undefined; - return appWrapperFeature.getWrapper(); -}; +export { createSecurityDocumentProfileProvider } from './profile'; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx new file mode 100644 index 0000000000000..1720d4cbffd08 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { getFieldValue } from '@kbn/discover-utils'; +import type { DocumentProfileProvider } from '../../../profiles'; +import { DocumentType, SolutionType } from '../../../profiles'; +import type { ProfileProviderServices } from '../../profile_provider_services'; +import { SECURITY_PROFILE_ID } from '../constants'; +import * as i18n from '../translations'; +import type { SecurityProfileProviderFactory } from '../types'; +import { AlertEventOverview } from '../components/alert_event_overview'; + +export const createSecurityDocumentProfileProvider: SecurityProfileProviderFactory< + DocumentProfileProvider +> = (_services: ProfileProviderServices) => { + return { + profileId: SECURITY_PROFILE_ID.document, + experimental: true, + profile: { + getDocViewer: (prev) => (params) => { + const prevDocViewer = prev(params); + const isAlert = getFieldValue(params.record, 'event.kind') === 'signal'; + + return { + ...prevDocViewer, + docViewsRegistry: (registry) => { + registry.add({ + id: 'doc_view_alerts_overview', + title: i18n.overviewTabTitle(isAlert), + order: 0, + component: AlertEventOverview, + }); + + return prevDocViewer.docViewsRegistry(registry); + }, + }; + }, + }, + resolve: ({ rootContext }) => { + if (rootContext.solutionType !== SolutionType.Security) { + return { isMatch: false }; + } + + return { + isMatch: true, + context: { + type: DocumentType.Default, + }, + }; + }, + }; +}; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx index ae117f087d73d..7d4517411f658 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx @@ -8,17 +8,14 @@ */ import type { FunctionComponent, PropsWithChildren } from 'react'; -import React from 'react'; import type { DataGridCellValueElementProps } from '@kbn/unified-data-table'; import type { RootProfileProvider } from '../../../profiles'; import { SolutionType } from '../../../profiles'; import type { ProfileProviderServices } from '../../profile_provider_services'; import type { SecurityProfileProviderFactory } from '../types'; import { createCellRendererAccessor } from '../accessors/get_cell_renderer_accessor'; -import { createAppWrapperAccessor } from '../accessors/create_app_wrapper_accessor'; import { getDefaultSecuritySolutionAppState } from '../accessors/get_default_app_state'; import { getAlertEventRowIndicator } from '../accessors/get_row_indicator'; -import { getRowAdditionalLeadingControls } from '../accessors/get_row_additional_control'; interface SecurityRootProfileContext { appWrapper?: FunctionComponent>; @@ -27,8 +24,6 @@ interface SecurityRootProfileContext { ) => FunctionComponent | undefined; } -const EmptyAppWrapper: FunctionComponent> = ({ children }) => <>{children}; - export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< RootProfileProvider > = (services: ProfileProviderServices) => { @@ -39,35 +34,12 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< return { profileId: 'security-root-profile', - isExperimental: true, profile: { - getRowAdditionalLeadingControls: (prev) => (params) => { - const additionalControls = prev(params) || []; - - return getRowAdditionalLeadingControls({ - services, - additionalControls, - }); - }, - getRenderAppWrapper: (PrevWrapper, params) => { - const AppWrapper = params.context.appWrapper ?? EmptyAppWrapper; - return ({ children }) => ( - - {children} - - ); - }, getCellRenderers: (prev, { context }) => (params) => { const entries = prev(params); - [ - 'host.name', - 'user.name', - 'source.ip', - 'destination.ip', - 'kibana.alert.workflow_status', - ].forEach((fieldName) => { + ['kibana.alert.workflow_status'].forEach((fieldName) => { entries[fieldName] = context.getSecuritySolutionCellRenderer?.(fieldName) ?? entries[fieldName]; }); @@ -83,14 +55,12 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< }; } - const getAppWrapper = await createAppWrapperAccessor(appWrapperFeature); const getCellRenderer = await createCellRendererAccessor(cellRendererFeature); return { isMatch: true, context: { solutionType: SolutionType.Security, - appWrapper: getAppWrapper?.(), getSecuritySolutionCellRenderer: getCellRenderer, }, }; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts index 18b3cf23beb6b..2fce388e73dcc 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts @@ -14,3 +14,22 @@ export const exploreRowActionLabel = (isAlert: boolean) => values: { isAlert }, defaultMessage: 'Explore {isAlert, select, true {Alert} other {Event}} in Security', }); + +export const overviewTabTitle = (isAlert: boolean) => + i18n.translate('discover.profile.security.flyout.overviewTabTitle', { + values: { isAlert }, + defaultMessage: '{isAlert, select, true {Alerts Overview} other {Event Overview}}', + }); + +export const overviewExploreButtonLabel = (isAlert: boolean) => + i18n.translate('discover.profile.security.flyout.overviewExploreButtonLabel', { + values: { isAlert }, + defaultMessage: 'Explore in {isAlert, select, true {Alerts} other {Timeline}}', + }); + +export const noEcsDescriptionReason = i18n.translate( + 'discover.profile.security.flyout.noEventKindDescriptionMessage', + { + defaultMessage: "This field doesn't have a description because it's not part of ECS.", + } +); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx index 4a5e1d9b3d956..e6c1bc254ca5c 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -8,7 +8,8 @@ */ import { encode } from '@kbn/rison'; -import type { EcsFlat } from '@elastic/ecs'; +import { EcsFlat } from '@elastic/ecs'; +import * as i18n from '../translations'; export interface CustomQuery { kind: 'kuery' | 'lucene'; @@ -73,3 +74,16 @@ export const getSecurityTimelineRedirectUrl = ({ return `${baseURL}?timeline=${encodedTimelineParam}&timerange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; }; + +/** + * Helper function to return the description of an allowed value of the specified field + * @param fieldName + * @param value + * @returns ecs description of the value + */ +export const getEcsAllowedValueDescription = (value: string): string => { + const allowedValues: EcsAllowedValue[] = EcsFlat['event.category']?.allowed_values ?? []; + const result = + allowedValues?.find((item) => item.name === value)?.description ?? i18n.noEcsDescriptionReason; + return result; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx b/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx index e276dde848565..193e3ae9db972 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx @@ -20,10 +20,7 @@ import type { import { AppStatus, DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { uiMetricService } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; -import type { - SecuritySolutionAppWrapperFeature, - SecuritySolutionCellRendererFeature, -} from '@kbn/discover-shared-plugin/public/services/discover_features'; +import type { SecuritySolutionCellRendererFeature } from '@kbn/discover-shared-plugin/public/services/discover_features'; import { ProductFeatureAssistantKey } from '@kbn/security-solution-features/src/product_features_keys'; import { getLazyCloudSecurityPosturePliAuthBlockExtension } from './cloud_security_posture/lazy_cloud_security_posture_pli_auth_block_extension'; import { getLazyEndpointAgentTamperProtectionExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_agent_tamper_protection_extension'; @@ -257,33 +254,7 @@ export class Plugin implements IPlugin { - const [coreStart, startPlugins] = await core.getStartServices(); - - const services = await this.services.generateServices(coreStart, startPlugins); - const subPlugins = await this.startSubPlugins(this.storage, coreStart, startPlugins); - const securityStoreForDiscover = await this.getStoreForDiscover( - coreStart, - startPlugins, - subPlugins - ); - - const { createSecuritySolutionDiscoverAppWrapperGetter } = - await this.getLazyDiscoverSharedDeps(); - - return createSecuritySolutionDiscoverAppWrapperGetter({ - core: coreStart, - services, - plugins: startPlugins, - store: securityStoreForDiscover, - }); - }, - }; - discoverFeatureRegistry.register(cellRendererFeature); - discoverFeatureRegistry.register(appWrapperFeature); } public async getLazyDiscoverSharedDeps() { From 8293ab06bd06cf95e3441e2f6f997b6866e0b9c8 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 20 Jun 2025 18:02:19 +0200 Subject: [PATCH 33/48] fix: tests --- .../plugins/shared/discover/tsconfig.json | 2 +- .../security/config.context_awareness.ts | 3 - .../context_awareness/cell_renderer.ts | 64 +++++++------- .../context_awareness/default_state.ts | 41 +++++---- .../ftr/discover/context_awareness/index.ts | 26 +++--- .../context_awareness/row_indicator.ts | 17 ++-- .../context_awareness/row_leading_controls.ts | 84 ------------------- .../ftr/discover/context_awareness/utils.ts | 18 ++++ 8 files changed, 93 insertions(+), 162 deletions(-) delete mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_leading_controls.ts create mode 100644 x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/utils.ts diff --git a/src/platform/plugins/shared/discover/tsconfig.json b/src/platform/plugins/shared/discover/tsconfig.json index 659b2cfd157e6..ebe5862ad42c1 100644 --- a/src/platform/plugins/shared/discover/tsconfig.json +++ b/src/platform/plugins/shared/discover/tsconfig.json @@ -104,7 +104,7 @@ "@kbn/shared-ux-page-analytics-no-data-types", "@kbn/core-application-browser-mocks", "@kbn/rison", - "@kbn/unified-tabs""@kbn/unified-tabs", + "@kbn/unified-tabs", "@kbn/unified-histogram", "@kbn/alerts-ui-shared", "@kbn/core-pricing-browser-mocks" diff --git a/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts b/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts index ab15b8d6beeb9..2e27f131a2bfb 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts @@ -14,9 +14,6 @@ export default createTestConfig({ reportName: 'Serverless Security Discover Context Awareness Functional Tests - Security Profiles', }, - kbnServerArgs: [ - `--discover.experimental.enabledProfiles=${JSON.stringify(['security-root-profile'])}`, - ], // include settings from project controller // https://github.com/elastic/elasticsearch-controller/blob/main/helm/values.yaml esServerArgs: ['xpack.ml.dfa.enabled=false'], diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts index 9fd3377ac3493..bbbd8344b5b0d 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/cell_renderer.ts @@ -5,9 +5,11 @@ * 2.0. */ -import kbnRison from '@kbn/rison'; import expect from '@kbn/expect'; +import { ServerlessRoleName } from '../../../../../../shared/lib'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { getDiscoverESQLState } from './utils'; +import { SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); @@ -16,47 +18,37 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('cell renderer', () => { before(async () => { - await PageObjects.svlCommonPage.loginAsAdmin(); + await PageObjects.svlCommonPage.loginWithRole(ServerlessRoleName.PLATFORM_ENGINEER); + await PageObjects.common.navigateToApp('security', { + path: 'alerts', + }); }); - describe('DataView mode', () => { - describe('cell renderer', () => { - it('should open host.name flyout with correct content', async () => { - await PageObjects.common.navigateToActualUrl('discover', undefined, { - ensureCurrentUrl: false, - }); - await queryBar.setQuery('host.name: "siem-kibana" AND event.kind: "signal"'); - await queryBar.clickQuerySubmitButton(); - await PageObjects.discover.waitUntilSearchingHasFinished(); - const hostName = await testSubjects.findAll('host-details-button', 2500); - expect(hostName).to.have.length(1); - await hostName[0].click(); - await testSubjects.existOrFail('host-panel-header', { timeout: 2500 }); - await testSubjects.existOrFail('asset-criticality-selector', { timeout: 2500 }); - await testSubjects.existOrFail('observedEntity-accordion', { timeout: 2500 }); + describe('ES|QL mode', () => { + it('should render alert workflow status badge', async () => { + const state = getDiscoverESQLState( + `from ${SECURITY_SOLUTION_DATA_VIEW} | WHERE host.name == "siem-kibana" and event.kind != "signal"` + ); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const alertWorkflowStatus = await testSubjects.findAll('rule-status-badge', 2500); + expect(alertWorkflowStatus).to.have.length(1); }); + }); - describe('ES|QL mode', () => { - it('should open host.name flyout', async () => { - const state = kbnRison.encode({ - dataSource: { type: 'esql' }, - query: { - esql: 'from auditbeat-2022 | WHERE host.name == "siem-kibana" and event.kind != "signal"', - }, - }); - await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { - ensureCurrentUrl: false, - }); - await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.dragFieldToTable('host.name'); - const hostName = await testSubjects.findAll('host-details-button', 2500); - expect(hostName).to.have.length(1); - await hostName[0].click(); - await testSubjects.existOrFail('host-panel-header', { timeout: 2500 }); - await testSubjects.existOrFail('asset-criticality-selector', { timeout: 2500 }); - await testSubjects.existOrFail('observedEntity-accordion', { timeout: 2500 }); + describe('DataView mode', () => { + it('should render alert workflow status badge', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, }); + await PageObjects.discover.selectIndexPattern(SECURITY_SOLUTION_DATA_VIEW); + await queryBar.setQuery('host.name: "siem-kibana" AND event.kind: "signal"'); + await queryBar.clickQuerySubmitButton(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const alertWorkflowStatus = await testSubjects.findAll('rule-status-badge', 2500); + expect(alertWorkflowStatus).to.have.length(1); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts index 84790ed750d23..71d6d256a41c5 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/default_state.ts @@ -5,9 +5,11 @@ * 2.0. */ -import kbnRison from '@kbn/rison'; import expect from '@kbn/expect'; +import { ServerlessRoleName } from '../../../../../../shared/lib'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { getDiscoverESQLState } from './utils'; +import { SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; const defaultEventColumns = [ '@timestamp', @@ -24,35 +26,42 @@ const defaultEventColumns = [ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); const queryBar = getService('queryBar'); + const retry = getService('retry'); - describe('row leading controls', () => { + describe('default State', () => { before(async () => { - await PageObjects.svlCommonPage.loginAsAdmin(); + await PageObjects.svlCommonPage.loginWithRole(ServerlessRoleName.PLATFORM_ENGINEER); + // creates security data view if it does not exist + await PageObjects.common.navigateToApp('security', { + path: 'alerts', + }); }); - describe('DataView mode', () => { + describe('ES|QL mode', () => { it('should have correct list of columns', async () => { - await PageObjects.common.navigateToActualUrl('discover', undefined, { + const state = getDiscoverESQLState(); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { ensureCurrentUrl: false, }); - await queryBar.setQuery('host.name: "siem-kibana"'); - await queryBar.clickQuerySubmitButton(); await PageObjects.discover.waitUntilSearchingHasFinished(); - expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be( - defaultEventColumns.join(', ') - ); + + await retry.try(async () => { + expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be( + defaultEventColumns.join(', ') + ); + }); }); }); - describe('ES|QL mode', () => { + describe('DataView mode', () => { it('should have correct list of columns', async () => { - const state = kbnRison.encode({ - dataSource: { type: 'esql' }, - }); - - await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + await PageObjects.common.navigateToActualUrl('discover', undefined, { ensureCurrentUrl: false, }); + + await PageObjects.discover.selectIndexPattern(SECURITY_SOLUTION_DATA_VIEW); + + await queryBar.clickQuerySubmitButton(); await PageObjects.discover.waitUntilSearchingHasFinished(); expect((await PageObjects.discover.getColumnHeaders()).join(', ')).to.be( defaultEventColumns.join(', ') diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts index 29bf8847d5066..2e29e1d8fb838 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/index.ts @@ -25,15 +25,6 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid this.tags(['esGate']); before(async () => { - await esArchiver.load( - 'src/platform/test/functional/fixtures/es_archiver/discover/context_awareness' - ); - await kibanaServer.importExport.load( - 'src/platform/test/functional/fixtures/kbn_archiver/discover/context_awareness' - ); - await kibanaServer.uiSettings.update({ - 'timepicker:timeDefaults': `{ "from": "${from}", "to": "${to}"}`, - }); await esArchiver.loadIfNeeded(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); const testRunUuid = uuidv4(); @@ -53,6 +44,16 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid risk_score: 70, }, }); + + await esArchiver.load( + 'src/platform/test/functional/fixtures/es_archiver/discover/context_awareness' + ); + await kibanaServer.importExport.load( + 'src/platform/test/functional/fixtures/kbn_archiver/discover/context_awareness' + ); + await kibanaServer.uiSettings.update({ + 'timepicker:timeDefaults': `{ "from": "${from}", "to": "${to}"}`, + }); }); after(async () => { @@ -68,9 +69,8 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid await esArchiver.unload(path.join(SECURITY_ES_ARCHIVES_DIR, 'auditbeat_single')); }); - // loadTestFile(require.resolve('./default_state')); - // loadTestFile(require.resolve('./cell_renderer')); - // loadTestFile(require.resolve('./row_indicator')); - loadTestFile(require.resolve('./row_leading_controls')); + loadTestFile(require.resolve('./default_state')); + loadTestFile(require.resolve('./cell_renderer')); + loadTestFile(require.resolve('./row_indicator')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts index dca62b49403b0..f70638f19c3bf 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_indicator.ts @@ -6,9 +6,10 @@ */ import expect from '@kbn/expect'; -import { encode } from '@kbn/rison'; +import { ServerlessRoleName } from '../../../../../../shared/lib'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; +import { getDiscoverESQLState } from './utils'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'svlCommonPage']); @@ -23,7 +24,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('row indicators', () => { describe('alerts and events', () => { before(async () => { - await PageObjects.svlCommonPage.loginAsAdmin(); + await PageObjects.svlCommonPage.loginWithRole(ServerlessRoleName.PLATFORM_ENGINEER); + await PageObjects.common.navigateToApp('security', { + path: 'alerts', + }); }); describe('DataView mode', () => { @@ -31,6 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToActualUrl('discover', undefined, { ensureCurrentUrl: false, }); + await PageObjects.discover.selectIndexPattern(SECURITY_SOLUTION_DATA_VIEW); await queryBar.clickQuerySubmitButton(); await PageObjects.discover.waitUntilSearchingHasFinished(); @@ -50,13 +55,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('ES|QL mode', () => { it('should have row indicator for both event and alert', async () => { - const query = `FROM ${SECURITY_SOLUTION_DATA_VIEW}`; - - const state = encode({ - datasource: { type: 'esql' }, - query: { esql: query }, - }); - + const state = getDiscoverESQLState(); await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { ensureCurrentUrl: false, }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_leading_controls.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_leading_controls.ts deleted file mode 100644 index 877ab5525fb8e..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/row_leading_controls.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import kbnRison from '@kbn/rison'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; -import { SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects([ - 'common', - 'timePicker', - 'discover', - 'svlCommonPage', - 'unifiedFieldList', - 'header', - ]); - const testSubjects = getService('testSubjects'); - const queryBar = getService('queryBar'); - - describe('row leading controls', () => { - before(async () => { - await PageObjects.svlCommonPage.loginAsAdmin(); - }); - - describe('DataView mode', () => { - it('should have explore event and alert in security leading action', async () => { - await PageObjects.common.navigateToActualUrl('discover', undefined, { - ensureCurrentUrl: false, - }); - await queryBar.setQuery('host.name: "siem-kibana"'); - await queryBar.clickQuerySubmitButton(); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - await PageObjects.discover.waitUntilSearchingHasFinished(); - const exploreInSecurityAction = await testSubjects.findAll( - 'unifiedDataTable_rowControl_additionalRowControl_exploreInSecurity', - 5000 - ); - expect(exploreInSecurityAction).to.have.length(2); - - expect(await exploreInSecurityAction[0].getAttribute('aria-label')).to.eql( - 'Explore Alert in Security' - ); - expect(await exploreInSecurityAction[1].getAttribute('aria-label')).to.eql( - 'Explore Event in Security' - ); - }); - }); - - describe('ES|QL mode', () => { - it('should have explore event in security leading action', async () => { - const query = `FROM ${SECURITY_SOLUTION_DATA_VIEW}`; - const state = kbnRison.encode({ - dataSource: { type: 'esql' }, - query: { esql: query }, - }); - - await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { - ensureCurrentUrl: false, - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - await PageObjects.discover.waitUntilSearchingHasFinished(); - const exploreInSecurityAction = await testSubjects.findAll( - 'unifiedDataTable_rowControl_additionalRowControl_exploreInSecurity', - 5000 - ); - expect(exploreInSecurityAction).to.have.length(2); - - expect(await exploreInSecurityAction[0].getAttribute('aria-label')).to.eql( - 'Explore Alert in Security' - ); - expect(await exploreInSecurityAction[1].getAttribute('aria-label')).to.eql( - 'Explore Event in Security' - ); - }); - }); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/utils.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/utils.ts new file mode 100644 index 0000000000000..ba5e1c9fa55c0 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/discover/context_awareness/utils.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import kbnRison from '@kbn/rison'; +import { SECURITY_SOLUTION_DATA_VIEW } from '../../../constants'; + +export const getDiscoverESQLState = (query?: string) => { + return kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: query ?? `FROM ${SECURITY_SOLUTION_DATA_VIEW} | WHERE host.name == "siem-kibana"`, + }, + }); +}; From a5221dc1f5afc2b59fd37f6296e6e6e76372ff62 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 20 Jun 2025 18:27:11 +0200 Subject: [PATCH 34/48] fix: readable url generations --- .../profile_providers/security/utils/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx index e6c1bc254ca5c..172a390d4b09a 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -72,7 +72,13 @@ export const getSecurityTimelineRedirectUrl = ({ const encodedTimelineTimerangeParam = encode(timelineTimerangeSearchParam); const encodedTimelineFlyoutParam = encode(timelineFlyoutSearchParam); - return `${baseURL}?timeline=${encodedTimelineParam}&timerange=${encodedTimelineTimerangeParam}&timelineFlyout=${encodedTimelineFlyoutParam}`; + const urlParams = new URLSearchParams({ + timeline: encodedTimelineParam, + timerange: encodedTimelineTimerangeParam, + timelineFlyout: encodedTimelineFlyoutParam, + }); + + return `${baseURL}?${urlParams.toString()}`; }; /** From bd76727b83a45d352e079859fc12e807e1e44044 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 20 Jun 2025 18:47:53 +0200 Subject: [PATCH 35/48] fix: remove dead code --- .../security_root_profile/profile.tsx | 1 - .../public/one_discover/app_wrapper/index.tsx | 131 ------------------ .../cell_renderers/cell_renderer.test.tsx | 10 +- .../cell_renderers/cell_renderers.tsx | 8 +- 4 files changed, 7 insertions(+), 143 deletions(-) delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/one_discover/app_wrapper/index.tsx diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx index 7d4517411f658..f6b546d5c88cd 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx @@ -30,7 +30,6 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< const { discoverShared } = services; const discoverFeaturesRegistry = discoverShared.features.registry; const cellRendererFeature = discoverFeaturesRegistry.getById('security-solution-cell-renderer'); - const appWrapperFeature = discoverFeaturesRegistry.getById('security-solution-app-wrapper'); return { profileId: 'security-root-profile', diff --git a/x-pack/solutions/security/plugins/security_solution/public/one_discover/app_wrapper/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/one_discover/app_wrapper/index.tsx deleted file mode 100644 index eb5c325475f2c..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/one_discover/app_wrapper/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React, { useMemo } from 'react'; -import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; -import { Provider as ReduxStoreProvider } from 'react-redux'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; -import { KibanaContextProvider, useKibana } from '@kbn/kibana-react-plugin/public'; -import { NavigationProvider } from '@kbn/security-solution-navigation'; -import type { CoreStart } from '@kbn/core/public'; -import type { SecuritySolutionAppWrapperFeature } from '@kbn/discover-shared-plugin/public'; -import type { DiscoverServices } from '@kbn/discover-plugin/public'; -import { CellActionsProvider } from '@kbn/cell-actions'; -import { APP_ID } from '../../../common'; -import { SecuritySolutionFlyout } from '../../flyout'; -import { StatefulEventContext } from '../../common/components/events_viewer/stateful_event_context'; -import type { SecurityAppStore } from '../../common/store'; -import { ReactQueryClientProvider } from '../../common/containers/query_client/query_client_provider'; -import type { StartPluginsDependencies, StartServices } from '../../types'; -import { MlCapabilitiesProvider } from '../../common/components/ml/permissions/ml_capabilities_provider'; -import { UserPrivilegesProvider } from '../../common/components/user_privileges/user_privileges_context'; -import { DiscoverInTimelineContextProvider } from '../../common/components/discover_in_timeline/provider'; -import { UpsellingProvider } from '../../common/components/upselling_provider'; -import { ConsoleManager } from '../../management/components/console'; -import { AssistantProvider } from '../../assistant/provider'; -import { ONE_DISCOVER_SCOPE_ID } from '../constants'; - -export const createSecuritySolutionDiscoverAppWrapperGetter = ({ - core, - services, - plugins, - store, -}: { - core: CoreStart; - services: StartServices; - plugins: StartPluginsDependencies; - /** - * instance of Security App store that should be used in Discover - */ - store: SecurityAppStore; -}) => { - const getSecuritySolutionDiscoverAppWrapper: Awaited< - ReturnType - > = () => { - return function SecuritySolutionDiscoverAppWrapper({ children }) { - const { services: discoverServices } = useKibana(); - const CasesContext = useMemo(() => plugins.cases.ui.getCasesContext(), []); - - const userCasesPermissions = useMemo(() => plugins.cases.helpers.canUseCases([APP_ID]), []); - - /** - * - * Since this component is meant to be used only in the context of Discover, - * these services are appended/overwritten to the existing services object - * provided by the Discover plugin. - * - */ - const securitySolutionServices: StartServices = useMemo( - () => ({ - ...services, - /* Helps with getting correct instance of query, timeFilter and filterManager instances from discover */ - data: discoverServices.data, - }), - [discoverServices] - ); - - const statefulEventContextValue = useMemo( - () => ({ - // timelineId acts as scopeId - timelineID: ONE_DISCOVER_SCOPE_ID, - enableHostDetailsFlyout: true, - /* behaviour similar to query tab */ - tabType: 'query', - enableIpDetailsFlyout: true, - }), - [] - ); - - return ( - - - - - - {/* ^_^ Needed for notes addition */} - - - {/* ^_^ Needed for Cell Actions since it gives errors when CellActionsContext is used */} - - {/* ^_^ Needed for Alert Preview from Expanded Section of Entity Flyout */} - - - - {/* ^_^ Needed for AlertPreview -> Alert Details Flyout Action */} - - {/* ^_^ Needed for AlertPreview -> Alert Details Flyout Action */} - - {/* ^_^ Needed for Add to Timeline action by `useRiskInputActions`*/} - - - {/* vv below context should not be here and should be removed */} - - {children} - - - - - - - - - - - - - - - - ); - }; - }; - - return getSecuritySolutionDiscoverAppWrapper; -}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderer.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderer.test.tsx index 03af5276aeea5..620f2b95e1220 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderer.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderer.test.tsx @@ -42,16 +42,17 @@ describe('getCellRendererForGivenRecord', () => { }); it('should return cell renderer correctly for allowed fields with correct data format', () => { - const cellRenderer = getCellRendererForGivenRecord('host.name'); + const cellRenderer = getCellRendererForGivenRecord('kibana.alert.workflow_status'); expect(cellRenderer).toBeDefined(); const props: DataGridCellValueElementProps = { - columnId: 'host.name', + columnId: 'kibana.alert.workflow_status', isDetails: false, isExpanded: false, row: { id: '1', raw: {}, flattened: { + 'kibana.alert.workflow_status': 'open', 'host.name': 'host1', 'user.name': 'user1', }, @@ -72,6 +73,7 @@ describe('getCellRendererForGivenRecord', () => { isTimeline: false, isDetails: false, data: [ + { field: 'kibana.alert.workflow_status', value: ['open'] }, { field: 'host.name', value: ['host1'] }, { field: 'user.name', value: ['user1'] }, ], @@ -79,7 +81,7 @@ describe('getCellRendererForGivenRecord', () => { scopeId: 'one-discover', linkValues: undefined, header: { - id: 'host.name', + id: 'kibana.alert.workflow_status', columnHeaderType: 'not-filtered', type: 'string', }, @@ -92,7 +94,7 @@ describe('getCellRendererForGivenRecord', () => { isExpandable: false, isExpanded: false, setCellProps: props.setCellProps, - columnId: 'host.name', + columnId: 'kibana.alert.workflow_status', }, {} ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx b/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx index 1259e519a68c6..bbb26666e2d2f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx @@ -24,13 +24,7 @@ export type SecuritySolutionRowCellRendererGetter = Awaited< * in Discover's contextual View * */ -const ALLOWED_DISCOVER_RENDERED_FIELDS = [ - 'host.name', - 'user.name', - 'source.ip', - 'destination.ip', - 'kibana.alert.workflow_status', -]; +const ALLOWED_DISCOVER_RENDERED_FIELDS = ['kibana.alert.workflow_status']; export const getCellRendererForGivenRecord: SecuritySolutionRowCellRendererGetter = ( fieldName: string From adaedf7a878f90cc5d21cb2c43f4c04b3b27d460 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 20 Jun 2025 18:55:22 +0200 Subject: [PATCH 36/48] fix: comment --- .../profile_providers/security/accessors/get_row_indicator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts index e716da8b9533d..70b341159ef7a 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_row_indicator.ts @@ -18,7 +18,6 @@ export const getAlertEventRowIndicator: NonNullable Date: Fri, 20 Jun 2025 19:14:43 +0200 Subject: [PATCH 37/48] fix: import --- .../plugins/security_solution/public/one_discover/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/one_discover/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/one_discover/index.tsx index a7aefd28551bf..2ec3ff99073df 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/one_discover/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/one_discover/index.tsx @@ -6,4 +6,3 @@ */ export { getCellRendererForGivenRecord } from './cell_renderers'; -export { createSecuritySolutionDiscoverAppWrapperGetter } from './app_wrapper'; From 106298da3f5d5e93c53af91601cb90b89d76e785 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Fri, 20 Jun 2025 19:54:10 +0200 Subject: [PATCH 38/48] fix: types --- .../security_solution/public/plugin.tsx | 33 ++----------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx b/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx index c814a2f67c822..273bfdb9e0a3c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx @@ -78,7 +78,6 @@ export class Plugin implements IPlugin, - plugins: SetupPlugins - ) { + public async registerDiscoverSharedFeatures(plugins: SetupPlugins) { const { discoverShared } = plugins; const discoverFeatureRegistry = discoverShared.features.registry; const cellRendererFeature: SecuritySolutionCellRendererFeature = { @@ -366,31 +362,6 @@ export class Plugin implements IPlugin { - if (!this._securityStoreForDiscover) { - const { createStoreFactory } = await this.lazyApplicationDependencies(); - - this._securityStoreForDiscover = await createStoreFactory( - coreStart, - startPlugins, - subPlugins, - this.storage, - this.experimentalFeatures - ); - } - if (startPlugins.timelines) { - startPlugins.timelines.setTimelineEmbeddedStore(this._securityStoreForDiscover); - } - return this._securityStoreForDiscover; - } - private async registerActions( store: SecurityAppStore, history: H.History, From 376295c8964369bddff993caec15739a5d5eb51a Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Sun, 22 Jun 2025 17:03:25 +0200 Subject: [PATCH 39/48] fix: test --- .../components/alert_event_overview.test.tsx | 32 +++++++++++-------- .../security/utils/index.tsx | 1 + 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx index 2b417700dd006..864faa1813884 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx @@ -15,6 +15,7 @@ import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { EcsFlat } from '@elastic/ecs'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { encode } from '@kbn/rison'; +import { URLSearchParams } from 'url'; jest.mock('../../../../hooks/use_discover_services'); @@ -43,7 +44,7 @@ const mockHit = { const mockDataView = dataViewMock; -describe('AlertEventOverviewAccessor', () => { +describe('AlertEventOverview', () => { beforeEach(() => { (useDiscoverServices as jest.Mock).mockReturnValue(mockDiscoverServices); }); @@ -102,24 +103,27 @@ describe('AlertEventOverviewAccessor', () => { } as unknown as DataTableRecord; render(); const expectedURLJSON = { - timeline: { + timeline: encode({ activeTab: 'query', isOpen: true, query: { expression: '_id: test-id', kind: 'kuery', }, - }, + }), - timeRange: { + timeRange: encode({ timeline: { - from: mockRow['@timestamp'], - to: mockRow['@timestamp'], - linkTo: false, + timerange: { + from: mockRow['@timestamp'], + to: mockRow['@timestamp'], + kind: 'absolute', + linkTo: false, + }, }, - }, + }), - timelineFlyout: { + timelineFlyout: encode({ right: { id: 'document-details-right', params: { @@ -127,13 +131,15 @@ describe('AlertEventOverviewAccessor', () => { scopeId: 'timeline-1', }, }, - }, + }), }; + const searchParams = new URLSearchParams( + `timeline=${expectedURLJSON.timeline}&timerange=${expectedURLJSON.timeRange}&timelineFlyout=${expectedURLJSON.timelineFlyout}` + ); + expect(screen.getByTestId('exploreSecurity').getAttribute('href')).toBe( - `test-timeline-url?timeline=${encode(expectedURLJSON.timeline)}&timeRange=${encode( - expectedURLJSON.timeRange - )}&timelineFlyout=${encode(expectedURLJSON.timelineFlyout)}` + `test-timeline-url?${searchParams}` ); }); }); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx index 172a390d4b09a..9a19777414125 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -41,6 +41,7 @@ export const getSecurityTimelineRedirectUrl = ({ from, to, kind: 'absolute', + linkTo: false, }, }, }; From 5dd1c79dc13557956eff1c222219eb7d43c3dbbc Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Sun, 22 Jun 2025 18:44:12 +0200 Subject: [PATCH 40/48] fix: cypress --- .../cypress/e2e/explore/urls/state.cy.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts index 923e88c1a4558..7d62d45398707 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts @@ -237,7 +237,7 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { 'contain', "/app/security/network?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + "&query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')" + - "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timeline=(activeTab:query,isOpen:!f,query:(expression:'',kind:kuery))" + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))" ); }); @@ -254,7 +254,7 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { 'contain', "/app/security/hosts?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + "&query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')" + - "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timeline=(activeTab:query,isOpen:!f,query:(expression:'',kind:kuery))" + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); cy.get(NETWORK) @@ -263,7 +263,7 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { 'contain', "/app/security/network?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + "&query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')" + - "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timeline=(activeTab:query,isOpen:!f,query:(expression:'',kind:kuery))" + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); toggleNavigationPanel(EXPLORE_PANEL_BTN); @@ -278,7 +278,7 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { .and( 'contain', "/app/security/hosts/name/siem-kibana/anomalies?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + - "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timeline=(activeTab:query,isOpen:!f,query:(expression:'',kind:kuery))" + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); @@ -289,7 +289,7 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { 'contain', "/app/security/hosts?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + "&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" + - "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timeline=(activeTab:query,isOpen:!f,query:(expression:'',kind:kuery))" + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); cy.get(BREADCRUMBS) @@ -299,7 +299,7 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { 'contain', "/app/security/hosts/name/siem-kibana?sourcerer=(default:(id:security-solution-default,selectedPatterns:!('auditbeat-*')))" + "&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" + - "&timeline=(activeTab:query,graphEventId:'',isOpen:!f,query:(expression:'',kind:kuery))" + + "&timeline=(activeTab:query,isOpen:!f,query:(expression:'',kind:kuery))" + "&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2023-01-01T21:33:29.186Z')))" ); }); From 31a7702550c5b86c8a81882aed16369bc15bacf1 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 23 Jun 2025 12:14:31 +0200 Subject: [PATCH 41/48] fix: translations --- .../components/alert_event_overview.tsx | 18 ++++++++++------ .../security/translations.ts | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx index 137d630685bb4..c49aa49a19e24 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx @@ -98,21 +98,27 @@ export const AlertEventOverview: DocViewerComponent = ({ hit }) => { style={{ paddingBlock: '20px' }} > - - {getEcsAllowedValueDescription(eventCategory)} + + + {getEcsAllowedValueDescription(eventCategory)} + {description ? ( - - {description} + + + {description} + ) : null} {isAlert ? ( - - {reason} + + + {reason} + ) : null} diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts index 2fce388e73dcc..0547a91ac7165 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts @@ -33,3 +33,24 @@ export const noEcsDescriptionReason = i18n.translate( defaultMessage: "This field doesn't have a description because it's not part of ECS.", } ); + +export const aboutSectionTitle = i18n.translate( + 'discover.profile.security.flyout.aboutSectionTitle', + { + defaultMessage: 'About', + } +); + +export const descriptionSectionTitle = i18n.translate( + 'discover.profile.security.flyout.descriptionSectionTitle', + { + defaultMessage: 'Description', + } +); + +export const reasonSectionTitle = i18n.translate( + 'discover.profile.security.flyout.reasonSectionTitle', + { + defaultMessage: 'Reason', + } +); From 582aaca6434bc1af841ee8ab52f0e9d5d664057b Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 23 Jun 2025 14:21:47 +0200 Subject: [PATCH 42/48] fix: default app state --- .../accessors/get_default_app_state.test.ts | 60 ++++++++++++++ .../accessors/get_default_app_state.ts | 81 ++++++++++--------- .../profile_providers/security/constants.ts | 2 + .../security_root_profile/profile.tsx | 8 +- 4 files changed, 113 insertions(+), 38 deletions(-) create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.test.ts diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.test.ts new file mode 100644 index 0000000000000..cce259f8cbf5d --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { getDefaultSecuritySolutionAppState } from './get_default_app_state'; + +describe('getDefaultSecuritySolutionAppState', () => { + it('should return default app state without security solution specific columns and breakdown field if there is no index match', () => { + const getDefaultAppState = getDefaultSecuritySolutionAppState(); + + const params = { + dataView: { + getIndexPattern: () => 'logs-*', + }, + }; + + const prevAppState = { someKey: 'someValue' }; + const prevAppStateGetter = () => prevAppState; + // @ts-expect-error - params should be compatible with the expected type + const appState = getDefaultAppState(prevAppStateGetter)(params); + + expect(Object.keys(appState)).toMatchObject(['someKey']); + }); + + it('should return default app state with security solution specific columns and breakdown field if there is index match', () => { + const getDefaultAppState = getDefaultSecuritySolutionAppState(); + + const params = { + dataView: { + getIndexPattern: () => '.alerts-security.alerts-*', + }, + }; + + const prevAppState = { someKey: 'someValue' }; + const prevAppStateGetter = () => prevAppState; + // @ts-expect-error - params should be compatible with the expected type + const appState = getDefaultAppState(prevAppStateGetter)(params); + + expect(appState).toEqual({ + ...prevAppState, + breakdownField: 'kibana.alert.workflow_status', + columns: [ + { name: '@timestamp', width: 218 }, + { name: 'kibana.alert.workflow_status' }, + { name: 'message', width: 360 }, + { name: 'event.category' }, + { name: 'event.action' }, + { name: 'host.name' }, + { name: 'source.ip' }, + { name: 'destination.ip' }, + { name: 'user.name' }, + ], + }); + }); +}); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts index b53be1751234b..c9e281e84c14b 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts @@ -7,40 +7,49 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { DefaultAppStateExtension } from '../../../types'; +import type { RootProfileProvider } from '../../../profiles'; +import { ALERTS_INDEX_PATTERN } from '../constants'; -export const getDefaultSecuritySolutionAppState: () => DefaultAppStateExtension = () => ({ - breakdownField: 'kibana.alert.workflow_status', - columns: [ - { - name: '@timestamp', - width: 218, - }, - { - name: 'kibana.alert.workflow_status', - width: 218, - }, - { - name: 'message', - width: 360, - }, - { - name: 'event.category', - }, - { - name: 'event.action', - }, - { - name: 'host.name', - }, - { - name: 'source.ip', - }, - { - name: 'destination.ip', - }, - { - name: 'user.name', - }, - ], -}); +export const getDefaultSecuritySolutionAppState: () => RootProfileProvider['profile']['getDefaultAppState'] = + () => (prev) => (params) => { + const { dataView } = params; + const appState = { ...prev(params) }; + if (!dataView.getIndexPattern().includes(ALERTS_INDEX_PATTERN)) { + return appState; + } + return { + ...appState, + breakdownField: 'kibana.alert.workflow_status', + columns: [ + { + name: '@timestamp', + width: 218, + }, + { + name: 'kibana.alert.workflow_status', + }, + { + name: 'message', + width: 360, + }, + { + name: 'event.category', + }, + { + name: 'event.action', + }, + { + name: 'host.name', + }, + { + name: 'source.ip', + }, + { + name: 'destination.ip', + }, + { + name: 'user.name', + }, + ], + }; + }; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts index 296a9e0ad32ae..0f701cb9102ba 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts @@ -11,3 +11,5 @@ export const SECURITY_PROFILE_ID = { root: 'security-root-profile', document: 'security-document-profile', }; + +export const ALERTS_INDEX_PATTERN = '.alerts-security.alerts-'; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx index f6b546d5c88cd..a49dd412256cc 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx @@ -16,6 +16,7 @@ import type { SecurityProfileProviderFactory } from '../types'; import { createCellRendererAccessor } from '../accessors/get_cell_renderer_accessor'; import { getDefaultSecuritySolutionAppState } from '../accessors/get_default_app_state'; import { getAlertEventRowIndicator } from '../accessors/get_row_indicator'; +import { ALERTS_INDEX_PATTERN, SECURITY_PROFILE_ID } from '../constants'; interface SecurityRootProfileContext { appWrapper?: FunctionComponent>; @@ -32,12 +33,15 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< const cellRendererFeature = discoverFeaturesRegistry.getById('security-solution-cell-renderer'); return { - profileId: 'security-root-profile', + profileId: SECURITY_PROFILE_ID.root, profile: { getCellRenderers: (prev, { context }) => (params) => { const entries = prev(params); + if (!params.dataView.getIndexPattern().includes(ALERTS_INDEX_PATTERN)) { + return entries; + } ['kibana.alert.workflow_status'].forEach((fieldName) => { entries[fieldName] = context.getSecuritySolutionCellRenderer?.(fieldName) ?? entries[fieldName]; @@ -45,7 +49,7 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< return entries; }, getRowIndicatorProvider: () => () => getAlertEventRowIndicator, - getDefaultAppState: () => () => getDefaultSecuritySolutionAppState(), + getDefaultAppState: getDefaultSecuritySolutionAppState(), }, resolve: async (params) => { if (params.solutionNavId !== SolutionType.Security) { From 2877773bdc2dc145676fc19a400088ea196b268a Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 23 Jun 2025 14:26:49 +0200 Subject: [PATCH 43/48] fix: conditional profile changes --- .../security/accessors/get_default_app_state.test.ts | 8 ++++---- .../security/accessors/get_default_app_state.ts | 2 +- .../security/security_root_profile/profile.tsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.test.ts index cce259f8cbf5d..cb51c727f205d 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.test.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.test.ts @@ -7,11 +7,11 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { getDefaultSecuritySolutionAppState } from './get_default_app_state'; +import { createDefaultSecuritySolutionAppStateGetter } from './get_default_app_state'; -describe('getDefaultSecuritySolutionAppState', () => { +describe('createDefaultSecuritySolutionAppStateGetter', () => { it('should return default app state without security solution specific columns and breakdown field if there is no index match', () => { - const getDefaultAppState = getDefaultSecuritySolutionAppState(); + const getDefaultAppState = createDefaultSecuritySolutionAppStateGetter(); const params = { dataView: { @@ -28,7 +28,7 @@ describe('getDefaultSecuritySolutionAppState', () => { }); it('should return default app state with security solution specific columns and breakdown field if there is index match', () => { - const getDefaultAppState = getDefaultSecuritySolutionAppState(); + const getDefaultAppState = createDefaultSecuritySolutionAppStateGetter(); const params = { dataView: { diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts index c9e281e84c14b..5556af186dc47 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/accessors/get_default_app_state.ts @@ -10,7 +10,7 @@ import type { RootProfileProvider } from '../../../profiles'; import { ALERTS_INDEX_PATTERN } from '../constants'; -export const getDefaultSecuritySolutionAppState: () => RootProfileProvider['profile']['getDefaultAppState'] = +export const createDefaultSecuritySolutionAppStateGetter: () => RootProfileProvider['profile']['getDefaultAppState'] = () => (prev) => (params) => { const { dataView } = params; const appState = { ...prev(params) }; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx index a49dd412256cc..cdb90d1c09180 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.tsx @@ -14,7 +14,7 @@ import { SolutionType } from '../../../profiles'; import type { ProfileProviderServices } from '../../profile_provider_services'; import type { SecurityProfileProviderFactory } from '../types'; import { createCellRendererAccessor } from '../accessors/get_cell_renderer_accessor'; -import { getDefaultSecuritySolutionAppState } from '../accessors/get_default_app_state'; +import { createDefaultSecuritySolutionAppStateGetter as createDefaultSecuritySolutionAppStateGetter } from '../accessors/get_default_app_state'; import { getAlertEventRowIndicator } from '../accessors/get_row_indicator'; import { ALERTS_INDEX_PATTERN, SECURITY_PROFILE_ID } from '../constants'; @@ -49,7 +49,7 @@ export const createSecurityRootProfileProvider: SecurityProfileProviderFactory< return entries; }, getRowIndicatorProvider: () => () => getAlertEventRowIndicator, - getDefaultAppState: getDefaultSecuritySolutionAppState(), + getDefaultAppState: createDefaultSecuritySolutionAppStateGetter(), }, resolve: async (params) => { if (params.solutionNavId !== SolutionType.Security) { From fade05580bf98b3a75b3d5dfdfc2e87516c59170 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 23 Jun 2025 19:52:42 +0200 Subject: [PATCH 44/48] fix: PR Feedback --- .../components/alert_event_overview.tsx | 3 ++- .../security/components/index.tsx | 19 ++++++++++++++ .../security_document_profile/profile.tsx | 4 +-- .../security/translations.ts | 2 +- .../security/utils/ecs_description.ts | 25 +++++++++++++++++++ .../security/utils/index.tsx | 17 ------------- 6 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/index.tsx create mode 100644 src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/ecs_description.ts diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx index c49aa49a19e24..c33210d17b7c0 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx @@ -21,7 +21,8 @@ import { EuiText, } from '@elastic/eui'; import * as i18n from '../translations'; -import { getEcsAllowedValueDescription, getSecurityTimelineRedirectUrl } from '../utils'; +import { getSecurityTimelineRedirectUrl } from '../utils'; +import { getEcsAllowedValueDescription } from '../utils/ecs_description'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; export const ExpandableSection: FC> = ({ diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/index.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/index.tsx new file mode 100644 index 0000000000000..ed1cb3aae3836 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/index.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { withSuspense } from '@kbn/shared-ux-utility'; +import { lazy } from 'react'; + +export const AlertEventOverviewLazy = withSuspense( + lazy(() => + import('./alert_event_overview').then((module) => ({ + default: module.AlertEventOverview, + })) + ) +); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx index 1720d4cbffd08..809d510913666 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_document_profile/profile.tsx @@ -14,7 +14,7 @@ import type { ProfileProviderServices } from '../../profile_provider_services'; import { SECURITY_PROFILE_ID } from '../constants'; import * as i18n from '../translations'; import type { SecurityProfileProviderFactory } from '../types'; -import { AlertEventOverview } from '../components/alert_event_overview'; +import { AlertEventOverviewLazy } from '../components'; export const createSecurityDocumentProfileProvider: SecurityProfileProviderFactory< DocumentProfileProvider @@ -34,7 +34,7 @@ export const createSecurityDocumentProfileProvider: SecurityProfileProviderFacto id: 'doc_view_alerts_overview', title: i18n.overviewTabTitle(isAlert), order: 0, - component: AlertEventOverview, + component: AlertEventOverviewLazy, }); return prevDocViewer.docViewsRegistry(registry); diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts index 0547a91ac7165..3d1dbe52dcf04 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts @@ -18,7 +18,7 @@ export const exploreRowActionLabel = (isAlert: boolean) => export const overviewTabTitle = (isAlert: boolean) => i18n.translate('discover.profile.security.flyout.overviewTabTitle', { values: { isAlert }, - defaultMessage: '{isAlert, select, true {Alerts Overview} other {Event Overview}}', + defaultMessage: '{isAlert, select, true {Alert Overview} other {Event Overview}}', }); export const overviewExploreButtonLabel = (isAlert: boolean) => diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/ecs_description.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/ecs_description.ts new file mode 100644 index 0000000000000..a26a787b324de --- /dev/null +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/ecs_description.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EcsFlat } from '@elastic/ecs'; +import * as i18n from '../translations'; +export type EcsAllowedValue = (typeof EcsFlat)['event.category']['allowed_values'][0]; + +/** + * Helper function to return the description of an allowed value of the specified field + * @param fieldName + * @param value + * @returns ecs description of the value + */ +export const getEcsAllowedValueDescription = (value: string): string => { + const allowedValues: EcsAllowedValue[] = EcsFlat['event.category']?.allowed_values ?? []; + const result = + allowedValues?.find((item) => item.name === value)?.description ?? i18n.noEcsDescriptionReason; + return result; +}; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx index 9a19777414125..8877dc3907c08 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/index.tsx @@ -8,16 +8,12 @@ */ import { encode } from '@kbn/rison'; -import { EcsFlat } from '@elastic/ecs'; -import * as i18n from '../translations'; export interface CustomQuery { kind: 'kuery' | 'lucene'; expression: string; } -export type EcsAllowedValue = (typeof EcsFlat)['event.category']['allowed_values'][0]; - export interface TimelineRedirectArgs { from?: string; to?: string; @@ -81,16 +77,3 @@ export const getSecurityTimelineRedirectUrl = ({ return `${baseURL}?${urlParams.toString()}`; }; - -/** - * Helper function to return the description of an allowed value of the specified field - * @param fieldName - * @param value - * @returns ecs description of the value - */ -export const getEcsAllowedValueDescription = (value: string): string => { - const allowedValues: EcsAllowedValue[] = EcsFlat['event.category']?.allowed_values ?? []; - const result = - allowedValues?.find((item) => item.name === value)?.description ?? i18n.noEcsDescriptionReason; - return result; -}; From 1ad7ae11935668d51821c224d323edffdb3eefe3 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 23 Jun 2025 20:04:29 +0200 Subject: [PATCH 45/48] fix: translations --- .../profile_providers/security/translations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts index 3d1dbe52dcf04..b20afd9b01eb9 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts @@ -18,7 +18,7 @@ export const exploreRowActionLabel = (isAlert: boolean) => export const overviewTabTitle = (isAlert: boolean) => i18n.translate('discover.profile.security.flyout.overviewTabTitle', { values: { isAlert }, - defaultMessage: '{isAlert, select, true {Alert Overview} other {Event Overview}}', + defaultMessage: '{isAlert, select, true {Alert} other {Event}} Overview', }); export const overviewExploreButtonLabel = (isAlert: boolean) => From 619e284afaef666d07ca8514273628ada3ca4ac4 Mon Sep 17 00:00:00 2001 From: Jatin Kathuria Date: Mon, 23 Jun 2025 20:44:45 +0200 Subject: [PATCH 46/48] remove extra enum key --- .../public/context_awareness/profiles/document_profile.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts b/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts index 446c26c302e2a..8f7350d863716 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profiles/document_profile.ts @@ -22,7 +22,6 @@ export enum DocumentType { Span = 'span', Transaction = 'transaction', Default = 'default', - Alert = 'alert', } /** From 8b8ba6907f4e720e699640562525974a650a18a7 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Mon, 23 Jun 2025 19:31:38 -0300 Subject: [PATCH 47/48] Fix failing tests after enabling security profile --- src/platform/test/functional/services/data_grid.ts | 12 +++++++++++- .../common/discover/group4/_adhoc_data_views.ts | 5 ++++- .../discover/search_source_alert.ts | 4 ++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/platform/test/functional/services/data_grid.ts b/src/platform/test/functional/services/data_grid.ts index 23946e9606af8..9362211aeb570 100644 --- a/src/platform/test/functional/services/data_grid.ts +++ b/src/platform/test/functional/services/data_grid.ts @@ -174,9 +174,13 @@ export class DataGridService extends FtrService { ); } + private getCellElementByColumnNameSelector(rowIndex: number, columnName: string) { + return `[data-test-subj="euiDataGridBody"] [data-test-subj="dataGridRowCell"][data-gridcell-column-id="${columnName}"][data-gridcell-visible-row-index="${rowIndex}"]`; + } + public async getCellElementByColumnName(rowIndex: number, columnName: string) { return await this.find.byCssSelector( - `[data-test-subj="euiDataGridBody"] [data-test-subj="dataGridRowCell"][data-gridcell-column-id="${columnName}"][data-gridcell-visible-row-index="${rowIndex}"]` + this.getCellElementByColumnNameSelector(rowIndex, columnName) ); } @@ -317,6 +321,12 @@ export class DataGridService extends FtrService { return await this.find.allByCssSelector(this.getCellElementSelector(rowIndex, columnIndex)); } + public async getAllCellElementsByColumnName(rowIndex: number, columnName: string) { + return await this.find.allByCssSelector( + this.getCellElementByColumnNameSelector(rowIndex, columnName) + ); + } + public async getDocCount(): Promise { const grid = await this.find.byCssSelector('[data-document-number]'); return Number(await grid.getAttribute('data-document-number')); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/group4/_adhoc_data_views.ts b/x-pack/test_serverless/functional/test_suites/common/discover/group4/_adhoc_data_views.ts index f2518dba547b0..bcf76ae6d2115 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/group4/_adhoc_data_views.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/group4/_adhoc_data_views.ts @@ -189,7 +189,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await addSearchToDashboard('logst*-ss-_bytes-runtimefield'); await addSearchToDashboard('logst*-ss-_bytes-runtimefield-updated'); - const [firstSearchCell, secondSearchCell] = await dataGrid.getAllCellElements(0, 3); + const [firstSearchCell, secondSearchCell] = await dataGrid.getAllCellElementsByColumnName( + 0, + '_bytes-runtimefield' + ); const first = await firstSearchCell.getVisibleText(); const second = await secondSearchCell.getVisibleText(); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts b/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts index 406630b382370..e99ad56328868 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts @@ -617,7 +617,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const selectedDataView = await dataViews.getSelectedName(); expect(selectedDataView).to.be.equal('search-source-*'); - const documentCell = await dataGrid.getCellElement(0, 3); + const documentCell = await dataGrid.getCellElementByColumnName(0, '_source'); const firstRowContent = await documentCell.getVisibleText(); expect(firstRowContent.includes('runtime-message-fieldmock-message')).to.be.equal(true); @@ -631,7 +631,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const selectedDataView = await dataViews.getSelectedName(); expect(selectedDataView).to.be.equal('search-source-*'); - const documentCell = await dataGrid.getCellElement(0, 3); + const documentCell = await dataGrid.getCellElementByColumnName(0, '_source'); const firstRowContent = await documentCell.getVisibleText(); expect(firstRowContent.includes('runtime-message-fieldmock-message')).to.be.equal(true); }); From e5507d419052479f7d967cf73c096dde3f7c8c5b Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Mon, 23 Jun 2025 19:39:01 -0300 Subject: [PATCH 48/48] Update stateful tests --- .../functional/apps/discover/group4/_adhoc_data_views.ts | 5 ++++- .../apps/discover_ml_uptime/discover/search_source_alert.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/platform/test/functional/apps/discover/group4/_adhoc_data_views.ts b/src/platform/test/functional/apps/discover/group4/_adhoc_data_views.ts index 310f5ea8dc3da..2e7cf0db098f0 100644 --- a/src/platform/test/functional/apps/discover/group4/_adhoc_data_views.ts +++ b/src/platform/test/functional/apps/discover/group4/_adhoc_data_views.ts @@ -186,7 +186,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await addSearchToDashboard('logst*-ss-_bytes-runtimefield'); await addSearchToDashboard('logst*-ss-_bytes-runtimefield-updated'); - const [firstSearchCell, secondSearchCell] = await dataGrid.getAllCellElements(0, 3); + const [firstSearchCell, secondSearchCell] = await dataGrid.getAllCellElementsByColumnName( + 0, + '_bytes-runtimefield' + ); const first = await firstSearchCell.getVisibleText(); const second = await secondSearchCell.getVisibleText(); diff --git a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts index 0393b058ecd73..75a4e57a8708b 100644 --- a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts +++ b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts @@ -596,7 +596,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const selectedDataView = await dataViews.getSelectedName(); expect(selectedDataView).to.be.equal('search-source-*'); - const documentCell = await dataGrid.getCellElement(0, 3); + const documentCell = await dataGrid.getCellElementByColumnName(0, '_source'); const firstRowContent = await documentCell.getVisibleText(); expect(firstRowContent.includes('runtime-message-fieldmock-message')).to.be.equal(true); @@ -610,7 +610,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const selectedDataView = await dataViews.getSelectedName(); expect(selectedDataView).to.be.equal('search-source-*'); - const documentCell = await dataGrid.getCellElement(0, 3); + const documentCell = await dataGrid.getCellElementByColumnName(0, '_source'); const firstRowContent = await documentCell.getVisibleText(); expect(firstRowContent.includes('runtime-message-fieldmock-message')).to.be.equal(true); });