From afffcdb0fa43b28e8fd280a214a6a16e871b9586 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 24 Mar 2025 13:00:05 +0100 Subject: [PATCH 01/36] progress --- .../shared/kbn-alerting-types/alerts_types.ts | 1 + .../apis/search_alerts/search_alerts.ts | 21 +- .../common/hooks/use_search_alerts_query.ts | 8 +- .../components/alerts_data_grid.tsx | 28 +- .../alerts-table/components/alerts_flyout.tsx | 12 +- .../components/default_alert_actions.tsx | 66 +++- .../mark_as_untracked_alert_action.tsx | 2 +- .../components/mute_alert_action.tsx | 19 +- .../view_alert_details_alert_action.tsx | 10 +- .../view_rule_details_alert_action.tsx | 2 +- .../plugins/observability/kibana.jsonc | 4 +- .../public/application/index.tsx | 7 +- .../alert_actions/alert_actions.tsx | 214 +++---------- .../alert_actions/use_case_actions.ts | 80 +++++ .../components/alerts_table/alerts_table.tsx | 2 - .../plugins/observability/public/index.ts | 3 - .../pages/alert_details/alert_details.tsx | 28 +- .../components/inspector_header_link.tsx | 32 ++ .../components/related_alerts.test.tsx | 75 ----- .../components/related_alerts.tsx | 235 -------------- .../related_alerts/related_alerts.tsx | 23 ++ .../related_alerts/related_alerts_view.tsx | 296 ++++++++++++++++++ .../use_build_related_alerts_query.ts | 185 +++++++++++ .../use_related_alerts_search.ts | 66 ++++ .../public/pages/cases/components/cases.tsx | 17 +- .../components/header_menu/header_menu.tsx | 5 +- .../header_menu/header_menu_portal.test.tsx | 30 -- .../header_menu/header_menu_portal.tsx | 43 --- .../plugins/observability/public/plugin.ts | 2 + 29 files changed, 879 insertions(+), 637 deletions(-) create mode 100644 x-pack/solutions/observability/plugins/observability/public/components/alert_actions/use_case_actions.ts create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/inspector_header_link.tsx delete mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.test.tsx delete mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.tsx create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_related_alerts_search.ts delete mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.test.tsx delete mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.tsx diff --git a/src/platform/packages/shared/kbn-alerting-types/alerts_types.ts b/src/platform/packages/shared/kbn-alerting-types/alerts_types.ts index 059815fe0dc61..1d7aff88d5d17 100644 --- a/src/platform/packages/shared/kbn-alerting-types/alerts_types.ts +++ b/src/platform/packages/shared/kbn-alerting-types/alerts_types.ts @@ -13,6 +13,7 @@ import { JsonValue } from '@kbn/utility-types'; export interface MetaAlertFields { _id: string; _index: string; + _score?: string | JsonValue[]; } export interface LegacyField { diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts index 17c5b3ddefb79..ef8d0e7b96262 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts @@ -7,7 +7,12 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { catchError, filter, lastValueFrom, map, of } from 'rxjs'; +import type { + MappingRuntimeFields, + QueryDslFieldAndFormat, + QueryDslQueryContainer, + SortCombinations, +} from '@elastic/elasticsearch/lib/api/types'; import type { Alert, EsQuerySnapshot, @@ -15,14 +20,9 @@ import type { RuleRegistrySearchRequest, RuleRegistrySearchResponse, } from '@kbn/alerting-types'; -import { set } from '@kbn/safer-lodash-set'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { - MappingRuntimeFields, - QueryDslFieldAndFormat, - QueryDslQueryContainer, - SortCombinations, -} from '@elastic/elasticsearch/lib/api/types'; +import { set } from '@kbn/safer-lodash-set'; +import { catchError, filter, lastValueFrom, map, of } from 'rxjs'; export interface SearchAlertsParams { // Dependencies @@ -68,6 +68,10 @@ export interface SearchAlertsParams { * The page size to fetch */ pageSize: number; + /** + * Force using the default context, otherwise use the AlertQueryContext + */ + useDefaultContext?: boolean; } export interface SearchAlertsResult { @@ -167,6 +171,7 @@ const parseAlerts = (rawResponse: RuleRegistrySearchResponse['rawResponse']) => acc.push({ ...hit.fields, _id: hit._id, + _score: hit._score, _index: hit._index, } as Alert); } diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts index 4450afa788c39..df1e629e7ed59 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts @@ -27,7 +27,11 @@ export const queryKeyPrefix = ['alerts', searchAlerts.name]; * When testing components that depend on this hook, prefer mocking the {@link searchAlerts} function instead of the hook itself. * @external https://tanstack.com/query/v4/docs/framework/react/guides/testing */ -export const useSearchAlertsQuery = ({ data, ...params }: UseSearchAlertsQueryParams) => { +export const useSearchAlertsQuery = ({ + data, + useDefaultContext, + ...params +}: UseSearchAlertsQueryParams) => { const { ruleTypeIds, consumers, @@ -60,7 +64,7 @@ export const useSearchAlertsQuery = ({ data, ...params }: UseSearchAlertsQueryPa pageSize, }), refetchOnWindowFocus: false, - context: AlertsQueryContext, + context: useDefaultContext === true ? undefined : AlertsQueryContext, enabled: ruleTypeIds.length > 0, // To avoid flash of empty state with pagination, see https://tanstack.com/query/latest/docs/framework/react/guides/paginated-queries#better-paginated-queries-with-placeholderdata keepPreviousData: true, diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_data_grid.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_data_grid.tsx index fce5457d926c8..ba8e5172a7031 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_data_grid.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_data_grid.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { FC, lazy, Suspense, useCallback, useMemo } from 'react'; +import React, { FC, useCallback, useMemo } from 'react'; import { EuiDataGrid, EuiDataGridControlColumn, @@ -28,14 +28,12 @@ import { AdditionalContext, AlertsDataGridProps, CellActionsOptions } from '../t import { useGetToolbarVisibility } from '../hooks/use_toolbar_visibility'; import { InspectButtonContainer } from './alerts_query_inspector'; import { typedMemo } from '../utils/react'; -import type { AlertsFlyout as AlertsFlyoutType } from './alerts_flyout'; +import { AlertsFlyout } from './alerts_flyout'; import { useBulkActions } from '../hooks/use_bulk_actions'; import { useSorting } from '../hooks/use_sorting'; import { CellPopoverHost } from './cell_popover_host'; import { NonVirtualizedGridBody } from './non_virtualized_grid_body'; -const AlertsFlyout = lazy(() => import('./alerts_flyout')) as typeof AlertsFlyoutType; - const defaultGridStyle: EuiDataGridStyle = { border: 'none', header: 'underline', @@ -336,18 +334,16 @@ export const AlertsDataGrid = typedMemo( return (
- - {flyoutAlertIndex > -1 && ( - - {...renderContext} - alert={alerts[flyoutAlertIndex]} - alertsCount={alertsCount} - onClose={handleFlyoutClose} - flyoutIndex={flyoutAlertIndex + pageIndex * pageSize} - onPaginate={onPaginateFlyout} - /> - )} - + {flyoutAlertIndex > -1 && ( + + {...renderContext} + alert={alerts[flyoutAlertIndex]} + alertsCount={alertsCount} + onClose={handleFlyoutClose} + flyoutIndex={flyoutAlertIndex + pageIndex * pageSize} + onPaginate={onPaginateFlyout} + /> + )} {alertsCount > 0 && ( ({ alert, ...renderContext -}: RenderContext & { +}: Omit< + RenderContext, + | 'oldAlertsData' + | 'ecsAlertsData' + | 'dataGridRef' + | 'browserFields' + | 'bulkActionsStore' + | 'openAlertInFlyout' +> & { alert: Alert; flyoutIndex: number; isLoading: boolean; @@ -72,7 +80,7 @@ export const AlertsFlyout = ({ () => Header ? ( - {...props} /> +
) : null, [Header, props] diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/default_alert_actions.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/default_alert_actions.tsx index 7317fdff1ff18..9d7c7de3142be 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/default_alert_actions.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/default_alert_actions.tsx @@ -10,38 +10,78 @@ import React from 'react'; import { useLoadRuleTypesQuery } from '@kbn/alerts-ui-shared/src/common/hooks'; import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { HttpStart } from '@kbn/core-http-browser'; +import type { NotificationsStart } from '@kbn/core-notifications-browser'; import { ViewRuleDetailsAlertAction } from './view_rule_details_alert_action'; import type { AdditionalContext, AlertActionsProps } from '../types'; import { ViewAlertDetailsAlertAction } from './view_alert_details_alert_action'; import { MuteAlertAction } from './mute_alert_action'; import { MarkAsUntrackedAlertAction } from './mark_as_untracked_alert_action'; -import { useAlertsTableContext } from '../contexts/alerts_table_context'; /** * Common alerts table row actions */ export const DefaultAlertActions = ( - props: AlertActionsProps + props: Pick< + AlertActionsProps, + | 'alert' + | 'openAlertInFlyout' + | 'onActionExecuted' + | 'isAlertDetailsEnabled' + | 'resolveAlertPagePath' + | 'tableId' + | 'resolveRulePagePath' + | 'refresh' + > ) => { - const { - services: { - http, - notifications: { toasts }, - }, - } = useAlertsTableContext(); + const { http, notifications } = useKibana<{ + http: HttpStart; + notifications: NotificationsStart; + }>().services; const { authorizedToCreateAnyRules } = useLoadRuleTypesQuery({ filteredRuleTypes: [], http, - toasts, + toasts: notifications.toasts, context: AlertsQueryContext, }); + const { + alert, + resolveRulePagePath, + refresh, + onActionExecuted, + tableId, + openAlertInFlyout, + isAlertDetailsEnabled, + resolveAlertPagePath, + } = props; + return ( <> - - - {authorizedToCreateAnyRules && } - {authorizedToCreateAnyRules && } + + + {authorizedToCreateAnyRules && ( + + )} + {authorizedToCreateAnyRules && ( + + )} ); }; diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx index 66f08e0b4d5ee..1b02d4c885f92 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx @@ -24,7 +24,7 @@ export const MarkAsUntrackedAlertAction = typedMemo( alert, refresh, onActionExecuted, - }: AlertActionsProps) => { + }: Pick, 'alert' | 'refresh' | 'onActionExecuted'>) => { const { services: { http, notifications }, } = useAlertsTableContext(); diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/mute_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/mute_alert_action.tsx index 8eedfaa2e653e..7a0018b8b8313 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/mute_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/mute_alert_action.tsx @@ -13,11 +13,13 @@ import { i18n } from '@kbn/i18n'; import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; import { useMuteAlertInstance } from '@kbn/response-ops-alerts-apis/hooks/use_mute_alert_instance'; import { useUnmuteAlertInstance } from '@kbn/response-ops-alerts-apis/hooks/use_unmute_alert_instance'; -import type { AdditionalContext, AlertActionsProps } from '../types'; -import { MUTE, UNMUTE } from '../translations'; -import { useAlertMutedState } from '../hooks/use_alert_muted_state'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { HttpStart } from '@kbn/core-http-browser'; +import type { NotificationsStart } from '@kbn/core-notifications-browser'; import { typedMemo } from '../utils/react'; -import { useAlertsTableContext } from '../contexts/alerts_table_context'; +import { useAlertMutedState } from '../hooks/use_alert_muted_state'; +import { MUTE, UNMUTE } from '../translations'; +import type { AdditionalContext, AlertActionsProps } from '../types'; /** * Alerts table row action to mute/unmute the selected alert @@ -27,10 +29,11 @@ export const MuteAlertAction = typedMemo( alert, refresh, onActionExecuted, - }: AlertActionsProps) => { - const { - services: { http, notifications }, - } = useAlertsTableContext(); + }: Pick, 'alert' | 'refresh' | 'onActionExecuted'>) => { + const { http, notifications } = useKibana<{ + http: HttpStart; + notifications: NotificationsStart; + }>().services; const { isMuted, ruleId, rule, alertInstanceId } = useAlertMutedState(alert); const { mutateAsync: muteAlert } = useMuteAlertInstance({ http, notifications }); const { mutateAsync: unmuteAlert } = useUnmuteAlertInstance({ http, notifications }); diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx index 7323615c505fa..c45b38753c617 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx @@ -26,7 +26,15 @@ export const ViewAlertDetailsAlertAction = typedMemo( isAlertDetailsEnabled, resolveAlertPagePath, tableId, - }: AlertActionsProps) => { + }: Pick< + AlertActionsProps, + | 'alert' + | 'openAlertInFlyout' + | 'onActionExecuted' + | 'isAlertDetailsEnabled' + | 'resolveAlertPagePath' + | 'tableId' + >) => { const { services: { http: { diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx index d1221aa40d34d..6b761bbba6632 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx @@ -23,7 +23,7 @@ export const ViewRuleDetailsAlertAction = typedMemo( alert, resolveRulePagePath, tableId, - }: AlertActionsProps) => { + }: Pick, 'alert' | 'resolveRulePagePath' | 'tableId'>) => { const { services: { http: { diff --git a/x-pack/solutions/observability/plugins/observability/kibana.jsonc b/x-pack/solutions/observability/plugins/observability/kibana.jsonc index 4a83799bfdb6e..5a33e6b6e411d 100644 --- a/x-pack/solutions/observability/plugins/observability/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability/kibana.jsonc @@ -45,7 +45,7 @@ "logsShared", "licensing", "navigation", - "fieldsMetadata" + "fieldsMetadata", ], "optionalPlugins": [ "discover", @@ -72,4 +72,4 @@ "common" ] } -} \ No newline at end of file +} diff --git a/x-pack/solutions/observability/plugins/observability/public/application/index.tsx b/x-pack/solutions/observability/plugins/observability/public/application/index.tsx index e19e729f39053..20e2ada9eaf74 100644 --- a/x-pack/solutions/observability/plugins/observability/public/application/index.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/application/index.tsx @@ -9,6 +9,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { PerformanceContextProvider } from '@kbn/ebt-tools'; +import { InspectorContextProvider } from '@kbn/observability-shared-plugin/public'; import { i18n } from '@kbn/i18n'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { AppMountParameters, APP_WRAPPER_CLASS, CoreStart } from '@kbn/core/public'; @@ -114,8 +115,10 @@ export const renderApp = ({ - - + + + + diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index 7f58f0157529f..6c5db666349c3 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -16,86 +16,45 @@ import { import React, { useMemo, useState, useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; -import { CaseAttachmentsWithoutOwner, CasesPublicStart } from '@kbn/cases-plugin/public'; -import { AttachmentType } from '@kbn/cases-plugin/common'; -import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { CasesPublicStart } from '@kbn/cases-plugin/public'; import { useRouteMatch } from 'react-router-dom'; import { SLO_ALERTS_TABLE_ID } from '@kbn/observability-shared-plugin/common'; import { DefaultAlertActions } from '@kbn/response-ops-alerts-table/components/default_alert_actions'; -import { useAlertsTableContext } from '@kbn/response-ops-alerts-table/contexts/alerts_table_context'; import { ALERT_UUID } from '@kbn/rule-data-utils'; -import type { EventNonEcsData } from '../../../common/typings'; -import { GetObservabilityAlertsTableProp } from '../alerts_table/types'; +import type { AlertActionsProps } from '@kbn/response-ops-alerts-table/types'; +import { useKibana } from '../../utils/kibana_react'; +import { useCaseActions } from './use_case_actions'; import { RULE_DETAILS_PAGE_ID } from '../../pages/rule_details/constants'; import { paths, SLO_DETAIL_PATH } from '../../../common/locators/paths'; import { parseAlert } from '../../pages/alerts/helpers/parse_alert'; -import { observabilityFeatureId } from '../..'; +import { observabilityFeatureId, ObservabilityRuleTypeRegistry } from '../..'; import { ALERT_DETAILS_PAGE_ID } from '../../pages/alert_details/alert_details'; -// eslint-disable-next-line react/function-component-definition -export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> = ({ - config, +export function AlertActions({ observabilityRuleTypeRegistry, alert, id, tableId, - dataGridRef, refresh, - isLoading, - isLoadingAlerts, - alerts, - oldAlertsData, - ecsAlertsData, - alertsCount, - browserFields, - isLoadingMutedAlerts, - mutedAlerts, - isLoadingCases, - cases, - isLoadingMaintenanceWindows, - maintenanceWindows, - pageIndex, - pageSize, openAlertInFlyout, - showAlertStatusWithFlapping, - bulkActionsStore, - columns, - renderCellValue, - renderCellPopover, - renderActionsCell, - renderFlyoutHeader, - renderFlyoutBody, - renderFlyoutFooter, -}) => { - const { services } = useAlertsTableContext(); +}: Pick & { + observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; + id: string; +}) { + const services = useKibana().services; + const { http: { basePath: { prepend }, }, } = services; const { - helpers: { getRuleIdFromEvent, canUseCases }, - hooks: { useCasesAddToNewCaseFlyout, useCasesAddToExistingCaseModal }, + helpers: { canUseCases }, } = services.cases! as unknown as CasesPublicStart; // Cases is guaranteed to be defined in Observability const isSLODetailsPage = useRouteMatch(SLO_DETAIL_PATH); const isInApp = Boolean(id === SLO_ALERTS_TABLE_ID && isSLODetailsPage); - const data = useMemo( - () => - Object.entries(alert ?? {}).reduce( - (acc, [field, value]) => [...acc, { field, value: value as string[] }], - [] - ), - [alert] - ); - const ecsData = useMemo( - () => ({ - _id: alert._id, - _index: alert._index, - }), - [alert._id, alert._index] - ); const userCasesPermissions = canUseCases([observabilityFeatureId]); const [viewInAppUrl, setViewInAppUrl] = useState(); @@ -125,128 +84,20 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> } }, [observabilityAlert.link, observabilityAlert.hasBasePath, prepend]); - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - - const caseAttachments: CaseAttachmentsWithoutOwner = useMemo(() => { - return ecsData?._id - ? [ - { - alertId: ecsData?._id ?? '', - index: ecsData?._index ?? '', - type: AttachmentType.alert, - rule: getRuleIdFromEvent({ ecs: ecsData, data: data ?? [] }), - }, - ] - : []; - }, [ecsData, getRuleIdFromEvent, data]); - - const onSuccess = useCallback(() => { - refresh(); - }, [refresh]); - - const createCaseFlyout = useCasesAddToNewCaseFlyout({ onSuccess }); - const selectCaseModal = useCasesAddToExistingCaseModal({ onSuccess }); + const { isPopoverOpen, setIsPopoverOpen, handleAddToExistingCaseClick, handleAddToNewCaseClick } = + useCaseActions({ + refresh, + alert, + }); - const closeActionsPopover = () => { + const closeActionsPopover = useCallback(() => { setIsPopoverOpen(false); - }; + }, [setIsPopoverOpen]); const toggleActionsPopover = () => { setIsPopoverOpen(!isPopoverOpen); }; - const handleAddToNewCaseClick = () => { - createCaseFlyout.open({ attachments: caseAttachments }); - closeActionsPopover(); - }; - - const handleAddToExistingCaseClick = () => { - selectCaseModal.open({ getAttachments: () => caseAttachments }); - closeActionsPopover(); - }; - - const defaultRowActions = useMemo( - () => ( - - currentPageId !== RULE_DETAILS_PAGE_ID ? paths.observability.ruleDetails(ruleId) : null - } - resolveAlertPagePath={(alertId, currentPageId) => - currentPageId !== ALERT_DETAILS_PAGE_ID ? paths.observability.alertDetails(alertId) : null - } - tableId={tableId} - dataGridRef={dataGridRef} - refresh={refresh} - isLoading={isLoading} - isLoadingAlerts={isLoadingAlerts} - alert={alert} - alerts={alerts} - oldAlertsData={oldAlertsData} - ecsAlertsData={ecsAlertsData} - alertsCount={alertsCount} - browserFields={browserFields} - isLoadingMutedAlerts={isLoadingMutedAlerts} - mutedAlerts={mutedAlerts} - isLoadingCases={isLoadingCases} - cases={cases} - isLoadingMaintenanceWindows={isLoadingMaintenanceWindows} - maintenanceWindows={maintenanceWindows} - pageIndex={pageIndex} - pageSize={pageSize} - openAlertInFlyout={openAlertInFlyout} - showAlertStatusWithFlapping={showAlertStatusWithFlapping} - bulkActionsStore={bulkActionsStore} - columns={columns} - renderCellValue={renderCellValue} - renderCellPopover={renderCellPopover} - renderActionsCell={renderActionsCell} - renderFlyoutHeader={renderFlyoutHeader} - renderFlyoutBody={renderFlyoutBody} - renderFlyoutFooter={renderFlyoutFooter} - services={services} - config={config} - observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} - /> - ), - [ - alert, - alerts, - alertsCount, - browserFields, - bulkActionsStore, - cases, - columns, - config, - dataGridRef, - ecsAlertsData, - isLoading, - isLoadingAlerts, - isLoadingCases, - isLoadingMaintenanceWindows, - isLoadingMutedAlerts, - maintenanceWindows, - mutedAlerts, - observabilityRuleTypeRegistry, - oldAlertsData, - openAlertInFlyout, - pageIndex, - pageSize, - refresh, - renderActionsCell, - renderCellPopover, - renderCellValue, - renderFlyoutBody, - renderFlyoutFooter, - renderFlyoutHeader, - services, - showAlertStatusWithFlapping, - tableId, - ] - ); - const actionsMenuItems = [ ...(userCasesPermissions.createComment && userCasesPermissions.read ? [ @@ -272,7 +123,28 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> , ] : []), - defaultRowActions, + useMemo( + () => ( + + currentPageId !== RULE_DETAILS_PAGE_ID ? paths.observability.ruleDetails(ruleId) : null + } + resolveAlertPagePath={(alertId, currentPageId) => + currentPageId !== ALERT_DETAILS_PAGE_ID + ? paths.observability.alertDetails(alertId) + : null + } + tableId={tableId} + refresh={refresh} + alert={alert} + openAlertInFlyout={openAlertInFlyout} + /> + ), + [alert, closeActionsPopover, openAlertInFlyout, refresh, tableId] + ), ]; const actionsToolTip = @@ -358,7 +230,7 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> ); -}; +} // Default export used for lazy loading // eslint-disable-next-line import/no-default-export diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/use_case_actions.ts b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/use_case_actions.ts new file mode 100644 index 0000000000000..e8025881e91c4 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/use_case_actions.ts @@ -0,0 +1,80 @@ +/* + * 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 { CaseAttachmentsWithoutOwner, CasesPublicStart } from '@kbn/cases-plugin/public'; +import { useCallback, useMemo, useState } from 'react'; +import { AttachmentType } from '@kbn/cases-plugin/common'; +import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import type { Alert } from '@kbn/alerting-types'; +import type { EventNonEcsData } from '../../../common/typings'; +import { useKibana } from '../../utils/kibana_react'; + +export const useCaseActions = ({ alert, refresh }: { alert: Alert; refresh?: () => void }) => { + const services = useKibana().services; + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const { + helpers: { getRuleIdFromEvent }, + hooks: { useCasesAddToNewCaseFlyout, useCasesAddToExistingCaseModal }, + } = services.cases! as unknown as CasesPublicStart; // Cases is guaranteed to be defined in Observability + + const onSuccess = useCallback(() => { + refresh?.(); + }, [refresh]); + + const selectCaseModal = useCasesAddToExistingCaseModal({ onSuccess }); + const ecsData = useMemo( + () => ({ + _id: alert._id, + _index: alert._index, + }), + [alert._id, alert._index] + ); + const data = useMemo( + () => + Object.entries(alert ?? {}).reduce( + (acc, [field, value]) => [...acc, { field, value: value as string[] }], + [] + ), + [alert] + ); + + const caseAttachments: CaseAttachmentsWithoutOwner = useMemo(() => { + return ecsData?._id + ? [ + { + alertId: ecsData?._id ?? '', + index: ecsData?._index ?? '', + type: AttachmentType.alert, + rule: getRuleIdFromEvent({ ecs: ecsData, data: data ?? [] }), + }, + ] + : []; + }, [ecsData, getRuleIdFromEvent, data]); + + const createCaseFlyout = useCasesAddToNewCaseFlyout({ onSuccess }); + const closeActionsPopover = () => { + setIsPopoverOpen(false); + }; + + const handleAddToNewCaseClick = () => { + createCaseFlyout.open({ attachments: caseAttachments }); + closeActionsPopover(); + }; + + const handleAddToExistingCaseClick = () => { + selectCaseModal.open({ getAttachments: () => caseAttachments }); + closeActionsPopover(); + }; + + return { + isPopoverOpen, + setIsPopoverOpen, + handleAddToExistingCaseClick, + handleAddToNewCaseClick, + }; +}; diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/alerts_table.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/alerts_table.tsx index 0c15c6f31ae36..dd8824f2a0100 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/alerts_table.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/alerts_table.tsx @@ -20,7 +20,6 @@ import { } from './types'; import { AlertsTableCellValue } from './common/cell_value'; import { AlertsFlyoutBody } from '../alerts_flyout/alerts_flyout_body'; -import { AlertsFlyoutHeader } from '../alerts_flyout/alerts_flyout_header'; import { AlertsFlyoutFooter } from '../alerts_flyout/alerts_flyout_footer'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { getColumns } from './common/get_columns'; @@ -68,7 +67,6 @@ export function ObservabilityAlertsTable(props: Omit import('./components/alerts_flyout/alerts_flyout')); - export type { Stat, Coordinates, diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.tsx index 3fc7997ce3e0a..b03d9d52da2a1 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.tsx @@ -35,7 +35,7 @@ import { AlertFieldsTable } from '@kbn/alerts-ui-shared/src/alert_fields_table'; import { css } from '@emotion/react'; import { omit } from 'lodash'; import { BetaBadge } from '../../components/experimental_badge'; -import { RelatedAlerts } from './components/related_alerts'; +import { RelatedAlerts } from './components/related_alerts/related_alerts'; import { AlertDetailsSource } from './types'; import { SourceBar } from './components'; import { StatusBar } from './components/status_bar'; @@ -296,7 +296,7 @@ export function AlertDetails() { ), 'data-test-subj': 'relatedAlertsTab', - content: , + content: , }, ]; @@ -333,15 +333,21 @@ export function AlertDetails() { }} data-test-subj="alertDetails" > - - - - tab.id === activeTabId)} - onTabClick={(tab) => handleSetTabId(tab.id as TabId)} - /> + + + + + tab.id === activeTabId)} + onTabClick={(tab) => handleSetTabId(tab.id as TabId)} + /> + ); } diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/inspector_header_link.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/inspector_header_link.tsx new file mode 100644 index 0000000000000..a25c9a9055900 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/inspector_header_link.tsx @@ -0,0 +1,32 @@ +/* + * 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 { EuiHeaderLink } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useInspectorContext } from '@kbn/observability-shared-plugin/public'; +import { useKibana } from '../../../utils/kibana_react'; + +export function InspectorHeaderLink() { + const { + services: { inspector }, + } = useKibana(); + + const { inspectorAdapters } = useInspectorContext(); + + const inspect = () => { + inspector.open(inspectorAdapters); + }; + + return ( + + {i18n.translate('xpack.observability.inspectButtonText', { + defaultMessage: 'Inspect', + })} + + ); +} diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.test.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.test.tsx deleted file mode 100644 index ce5a404710ad8..0000000000000 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.test.tsx +++ /dev/null @@ -1,75 +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 from 'react'; -import { render } from '../../../utils/test_helper'; -import { alertWithGroupsAndTags } from '../mock/alert'; -import { useKibana } from '../../../utils/kibana_react'; -import { kibanaStartMock } from '../../../utils/kibana_react.mock'; -import { RelatedAlerts } from './related_alerts'; -import { ObservabilityAlertsTable } from '../../../components/alerts_table/alerts_table_lazy'; -import { - OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES, - observabilityAlertFeatureIds, -} from '../../../../common/constants'; - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: jest.fn(), -})); - -jest.mock('../../../utils/kibana_react'); - -jest.mock('../../../components/alerts_table/alerts_table_lazy'); -const mockAlertsTable = jest.mocked(ObservabilityAlertsTable).mockReturnValue(
); - -jest.mock('@kbn/alerts-grouping', () => ({ - AlertsGrouping: jest.fn().mockImplementation(({ children }) =>
{children([])}
), -})); - -const useKibanaMock = useKibana as jest.Mock; -const mockKibana = () => { - const services = kibanaStartMock.startContract().services; - services.spaces.getActiveSpace = jest - .fn() - .mockImplementation(() => - Promise.resolve({ id: 'space-id', name: 'space-name', disabledFeatures: [] }) - ); - useKibanaMock.mockReturnValue({ - services: { - ...services, - http: { - basePath: { - prepend: jest.fn(), - }, - }, - }, - }); -}; - -describe('Related alerts', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockKibana(); - }); - - it('should pass the correct configuration options to the alerts table', async () => { - render(); - - expect(mockAlertsTable).toHaveBeenLastCalledWith( - expect.objectContaining({ - id: 'xpack.observability.related.alerts.table', - ruleTypeIds: OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES, - consumers: observabilityAlertFeatureIds, - initialPageSize: 50, - renderAdditionalToolbarControls: expect.any(Function), - showInspectButton: true, - }), - expect.anything() - ); - }); -}); diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.tsx deleted file mode 100644 index 54c16b219d952..0000000000000 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.tsx +++ /dev/null @@ -1,235 +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, { useState, useRef, useEffect } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; -import { - ALERT_END, - ALERT_GROUP, - ALERT_RULE_UUID, - ALERT_START, - ALERT_UUID, - TAGS, -} from '@kbn/rule-data-utils'; -import { BoolQuery, Filter } from '@kbn/es-query'; -import { AlertsGrouping } from '@kbn/alerts-grouping'; -import { GroupingToolbarControls } from '../../../components/alerts_table/grouping/grouping_toolbar_controls'; -import { ObservabilityFields } from '../../../../common/utils/alerting/types'; - -import { - OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES, - observabilityAlertFeatureIds, -} from '../../../../common/constants'; -import { - getRelatedAlertKuery, - getSharedFields, -} from '../../../../common/utils/alerting/get_related_alerts_query'; -import { ObservabilityAlertsTable, TopAlert } from '../../..'; -import { - AlertSearchBarContainerState, - DEFAULT_STATE, -} from '../../../components/alert_search_bar/containers/state_container'; -import { ObservabilityAlertSearchbarWithUrlSync } from '../../../components/alert_search_bar/alert_search_bar_with_url_sync'; -import { renderGroupPanel } from '../../../components/alerts_table/grouping/render_group_panel'; -import { getGroupStats } from '../../../components/alerts_table/grouping/get_group_stats'; -import { getAggregationsByGroupingField } from '../../../components/alerts_table/grouping/get_aggregations_by_grouping_field'; -import { DEFAULT_GROUPING_OPTIONS } from '../../../components/alerts_table/grouping/constants'; -import { ACTIVE_ALERTS, ALERT_STATUS_FILTER } from '../../../components/alert_search_bar/constants'; -import { AlertsByGroupingAgg } from '../../../components/alerts_table/types'; -import { - alertSearchBarStateContainer, - Provider, - useAlertSearchBarStateContainer, -} from '../../../components/alert_search_bar/containers'; -import { RELATED_ALERTS_TABLE_CONFIG_ID, SEARCH_BAR_URL_STORAGE_KEY } from '../../../constants'; -import { useKibana } from '../../../utils/kibana_react'; -import { buildEsQuery } from '../../../utils/build_es_query'; -import { mergeBoolQueries } from '../../alerts/helpers/merge_bool_queries'; -import icon from './assets/illustration_product_no_results_magnifying_glass.svg'; - -const ALERTS_PER_PAGE = 50; -const RELATED_ALERTS_SEARCH_BAR_ID = 'related-alerts-search-bar-o11y'; -const ALERTS_TABLE_ID = 'xpack.observability.related.alerts.table'; - -interface Props { - alert?: TopAlert; -} - -// TODO: Bring back setting default status filter as active -const defaultState: AlertSearchBarContainerState = { ...DEFAULT_STATE }; -const DEFAULT_FILTERS: Filter[] = []; - -export function InternalRelatedAlerts({ alert }: Props) { - const kibanaServices = useKibana().services; - const { http, notifications, dataViews } = kibanaServices; - const alertSearchBarStateProps = useAlertSearchBarStateContainer(SEARCH_BAR_URL_STORAGE_KEY, { - replace: false, - }); - - const [esQuery, setEsQuery] = useState<{ bool: BoolQuery }>(); - const alertStart = alert?.fields[ALERT_START]; - const alertEnd = alert?.fields[ALERT_END]; - const alertId = alert?.fields[ALERT_UUID]; - const tags = alert?.fields[TAGS]; - const groups = alert?.fields[ALERT_GROUP]; - const ruleId = alert?.fields[ALERT_RULE_UUID]; - const sharedFields = getSharedFields(alert?.fields); - const kuery = getRelatedAlertKuery({ tags, groups, ruleId, sharedFields }); - - const defaultFilters = useRef([ - { - query: { - match_phrase: { - 'kibana.alert.uuid': alertId, - }, - }, - meta: { - negate: true, - }, - }, - ]); - - useEffect(() => { - if (alertStart) { - const defaultTimeRange = getPaddedAlertTimeRange(alertStart, alertEnd); - alertSearchBarStateProps.onRangeFromChange(defaultTimeRange.from); - alertSearchBarStateProps.onRangeToChange(defaultTimeRange.to); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [alertStart, alertEnd]); - - if (!kuery || !alert) return ; - - return ( - - - - - - - {esQuery && ( - - ruleTypeIds={OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES} - consumers={observabilityAlertFeatureIds} - defaultFilters={ - ALERT_STATUS_FILTER[alertSearchBarStateProps.status ?? ACTIVE_ALERTS.status] ?? - DEFAULT_FILTERS - } - from={alertSearchBarStateProps.rangeFrom} - to={alertSearchBarStateProps.rangeTo} - globalFilters={alertSearchBarStateProps.filters ?? DEFAULT_FILTERS} - globalQuery={{ query: alertSearchBarStateProps.kuery, language: 'kuery' }} - groupingId={RELATED_ALERTS_TABLE_CONFIG_ID} - defaultGroupingOptions={DEFAULT_GROUPING_OPTIONS} - getAggregationsByGroupingField={getAggregationsByGroupingField} - renderGroupPanel={renderGroupPanel} - getGroupStats={getGroupStats} - services={{ - notifications, - dataViews, - http, - }} - > - {(groupingFilters) => { - const groupQuery = buildEsQuery({ - filters: groupingFilters, - }); - return ( - ( - - )} - showInspectButton - /> - ); - }} - - )} - - - ); -} - -const heights = { - tall: 490, - short: 250, -}; -const panelStyle = { - maxWidth: 500, -}; - -function EmptyState() { - return ( - - - - - - - - -

- -

-
-

- -

-
-
- - - -
-
-
-
-
- ); -} - -export function RelatedAlerts(props: Props) { - return ( - - - - ); -} diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx new file mode 100644 index 0000000000000..c0b5125313780 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx @@ -0,0 +1,23 @@ +/* + * 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 from 'react'; +import { EuiLoadingChart } from '@elastic/eui'; +import { RelatedAlertsView } from './related_alerts_view'; +import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; + +interface Props { + alertData?: AlertData | null; +} + +export function RelatedAlerts({ alertData }: Props) { + if (!alertData) { + return ; + } + + return ; +} diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx new file mode 100644 index 0000000000000..9ba905e4a002b --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx @@ -0,0 +1,296 @@ +/* + * 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 { + EuiBadge, + EuiInMemoryTable, + EuiBasicTableColumn, + EuiButton, + EuiCallOut, + EuiFlexGroup, + EuiTableSelectionType, + EuiText, + EuiSearchBarProps, + EuiSpacer, + EuiButtonEmpty, +} from '@elastic/eui'; +import type { Alert } from '@kbn/alerting-types'; +import { i18n } from '@kbn/i18n'; +import { + ALERT_INSTANCE_ID, + ALERT_REASON, + ALERT_RULE_CATEGORY, + ALERT_RULE_NAME, + ALERT_RULE_TAGS, + ALERT_RULE_UUID, + ALERT_STATUS, + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, +} from '@kbn/rule-data-utils'; +import { intersection, isEmpty } from 'lodash'; +import React, { useState } from 'react'; +import AlertsFlyout from '@kbn/response-ops-alerts-table/components/alerts_flyout'; +import AlertActions from '../../../../components/alert_actions/alert_actions'; +import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; +import { ObservabilityRuleTypeRegistry } from '../../../..'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; +import { useKibana } from '../../../../utils/kibana_react'; +import { useRelatedAlertsSearch } from '../../hooks/related_alerts/use_related_alerts_search'; +import { AlertsFlyoutBody } from '../../../../components/alerts_flyout/alerts_flyout_body'; +import { AlertsFlyoutFooter } from '../../../../components/alerts_flyout/alerts_flyout_footer'; + +interface Props { + alertData: AlertData; +} + +export function RelatedAlertsView({ alertData }: Props) { + const { formatted: alert } = alertData; + const { data, isLoading, isError } = useRelatedAlertsSearch({ alert }); + + const [flyoutAlert, setFlyoutAlert] = useState(null); + const [flyoutAlertIndex, setFlyoutAlertIndex] = useState(0); + const { observabilityRuleTypeRegistry } = usePluginContext(); + + const services = useKibana().services; + + const columns: Array> = [ + { + name: 'Actions', + width: '100px', + render: (item: Alert) => { + return ( + {}} + observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} + openAlertInFlyout={(alertId: string) => { + setFlyoutAlert(item); + const flyoutIndex = data?.alerts.findIndex((al) => al._id === item._id); + setFlyoutAlertIndex(flyoutIndex ?? 0); + }} + /> + ); + }, + }, + { + field: ALERT_STATUS, + name: 'Status', + render: (_, item: Alert) => { + const value = getAlertFieldValue(item, ALERT_STATUS); + if (value !== ALERT_STATUS_ACTIVE && value !== ALERT_STATUS_RECOVERED) { + // NOTE: This should only be needed to narrow down the type. + // Status should be either "active" or "recovered". + return null; + } + return {value}; + }, + width: '150', + }, + { + field: ALERT_RULE_NAME, + name: 'Rule', + truncateText: true, + render: (_, item: Alert) => { + const ruleName = getAlertFieldValue(item, ALERT_RULE_NAME); + const ruleType = getAlertFieldValue(item, ALERT_RULE_CATEGORY); + return ( +
+ { + setFlyoutAlert(item); + const flyoutIndex = data?.alerts.findIndex((al) => al._id === item._id); + setFlyoutAlertIndex(flyoutIndex ?? 0); + }} + > + {ruleName} + + {ruleType} +
+ ); + }, + }, + { + field: ALERT_INSTANCE_ID, + name: 'Group', + truncateText: true, + render: (_, item: Alert) => { + const instanceId = getAlertFieldValue(item, ALERT_INSTANCE_ID); + return {instanceId}; + }, + }, + { + field: 'relation', + name: i18n.translate('xpack.observability.relatedAlertsView.relation', { + defaultMessage: 'Relation', + }), + render: (_, item: Alert) => { + const instanceId = getAlertFieldValue(item, ALERT_INSTANCE_ID); + const tags = getAlertFieldValue(item, ALERT_RULE_TAGS); + const ruleUuid = getAlertFieldValue(item, ALERT_RULE_UUID); + const hasSomeRelationWithInstance = + intersection(alert.fields[ALERT_INSTANCE_ID].split(','), instanceId.split(',')).length > + 0; + const hasSomeRelationWithTags = + intersection(alert.fields[ALERT_RULE_TAGS], tags.split(',')).length > 0; + const hasRelationWithRule = ruleUuid === alert.fields[ALERT_RULE_UUID]; + return ( + <> + {hasSomeRelationWithInstance && ( + + {i18n.translate('xpack.observability.columns.groupsBadgeLabel', { + defaultMessage: 'Groups', + })} + + )} + {hasSomeRelationWithTags && ( + + {i18n.translate('xpack.observability.columns.tagsBadgeLabel', { + defaultMessage: 'Tags', + })} + + )} + {hasRelationWithRule && ( + + {i18n.translate('xpack.observability.columns.ruleBadgeLabel', { + defaultMessage: 'Rule', + })} + + )} + + ); + }, + }, + ]; + + const [selectedAlerts, setSelectedAlerts] = useState([]); + const [pageIndex, setPageIndex] = useState(0); + const onSelectionChange = (selected: Alert[]) => { + setSelectedAlerts(selected); + }; + const selection: EuiTableSelectionType = { + onSelectionChange, + selectable: () => true, + }; + + const renderToolsRight = () => { + return [ + {}} + disabled={selectedAlerts.length === 0} + > + {i18n.translate('xpack.observability.relatedAlertsView.openCaseForSelectedButtonLabel', { + defaultMessage: 'Open case for {count} selected alerts', + values: { + count: selectedAlerts.length, + }, + })} + , + ]; + }; + + const search: EuiSearchBarProps = { + toolsLeft: renderToolsRight(), + box: { + incremental: true, + }, + filters: [], + }; + + return ( + + + +

+ {i18n.translate('xpack.observability.relatedAlertsView.p.weAreFetchingAlertsLabel', { + defaultMessage: + "We are fetching relevant alerts to the current alert based on some heuristics. Soon you'll be able to tweaks the weights applied to these heuristics", + })} +

+
+ + itemId="_id" + search={search} + tableCaption={MOST_RELEVANT_ALERTS} + items={data?.alerts ?? []} + rowHeader={ALERT_REASON} + columns={columns} + loading={isLoading} + error={isError ? ERROR_FETCHING : undefined} + selection={selection} + tableLayout="auto" + pagination={true} + /> + {flyoutAlert && ( + + flyoutIndex={flyoutAlertIndex} + onClose={() => setFlyoutAlert(null)} + isLoading={isLoading} + alert={flyoutAlert} + alerts={data?.alerts ?? []} + onPaginate={(page) => { + setPageIndex(page); + setFlyoutAlertIndex(page); + setFlyoutAlert(data?.alerts[page] ?? null); + }} + isLoadingAlerts={isLoading} + refresh={() => {}} + alertsCount={data?.alerts.length ?? 0} + isLoadingMutedAlerts={false} + isLoadingCases={false} + isLoadingMaintenanceWindows={false} + pageIndex={pageIndex} + pageSize={0} + services={services} + columns={[]} + renderFlyoutBody={AlertsFlyoutBody} + renderFlyoutFooter={AlertsFlyoutFooter} + observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} + /> + )} +
+ ); +} + +const MOST_RELEVANT_ALERTS = i18n.translate('xpack.observability.relatedAlertsView.mostRelevant', { + defaultMessage: 'Most relevant alerts to the current one', +}); + +const ERROR_FETCHING = i18n.translate('xpack.observability.relatedAlertsView.error', { + defaultMessage: 'Error fetching relevant alerts', +}); + +export const getAlertFieldValue = (alert: Alert, fieldName: string) => { + // can be updated when working on https://github.com/elastic/kibana/issues/140819 + const rawValue = alert[fieldName]; + const value = Array.isArray(rawValue) ? rawValue.join() : rawValue; + + if (!isEmpty(value)) { + if (typeof value === 'object') { + try { + return JSON.stringify(value); + } catch (e) { + return 'Error: Unable to parse JSON value.'; + } + } + return value; + } + + return '--'; +}; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts new file mode 100644 index 0000000000000..b360a151d50c0 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts @@ -0,0 +1,185 @@ +/* + * 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { + ALERT_END, + ALERT_GROUP, + ALERT_INSTANCE_ID, + ALERT_RULE_TAGS, + ALERT_RULE_UUID, + ALERT_START, + ALERT_STATUS, + ALERT_UUID, +} from '@kbn/rule-data-utils'; +import dedent from 'dedent'; +import moment from 'moment'; +import { ObservabilityFields } from '../../../../../common/utils/alerting/types'; +import { TopAlert } from '../../../../typings/alerts'; + +interface Props { + alert: TopAlert; +} + +export function useBuildRelatedAlertsQuery({ alert }: Props): QueryDslQueryContainer { + const groups = alert.fields[ALERT_GROUP]; + const shouldGroups: QueryDslQueryContainer[] = []; + groups?.forEach(({ field, value }) => { + if (!field || !value) return; + shouldGroups.push({ + bool: { + boost: 2.0, + must: [ + { term: { 'kibana.alert.group.field': field } }, + { term: { 'kibana.alert.group.value': value } }, + ], + }, + }); + }); + + const shouldRule = alert.fields[ALERT_RULE_UUID] + ? [ + { + term: { + 'kibana.alert.rule.uuid': { + value: alert.fields[ALERT_RULE_UUID], + boost: 1.0, + }, + }, + }, + ] + : []; + const startDate = moment(alert.fields[ALERT_START]); + const endDate = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]) : undefined; + const tags = alert.fields[ALERT_RULE_TAGS] ?? []; + const instanceId = alert.fields[ALERT_INSTANCE_ID]?.split(',') ?? []; + + return { + bool: { + filter: [ + { + range: { + [ALERT_START]: { + gte: startDate.clone().subtract(1, 'days').toISOString(), + lte: startDate.clone().add(1, 'days').toISOString(), + }, + }, + }, + ], + must_not: [ + { + term: { + [ALERT_UUID]: { + value: alert.fields[ALERT_UUID], + }, + }, + }, + ], + should: [ + ...shouldGroups, + ...shouldRule, + { + term: { + [ALERT_STATUS]: { + value: alert.fields[ALERT_STATUS], + boost: 2, + }, + }, + }, + { + function_score: { + functions: [ + { + exp: { + [ALERT_START]: { + origin: startDate.toISOString(), + scale: '10m', + offset: '10m', + decay: 0.5, + }, + }, + weight: 10, + }, + ...(endDate + ? [ + { + exp: { + [ALERT_END]: { + origin: endDate.toISOString(), + scale: '10m', + offset: '10m', + decay: 0.5, + }, + }, + weight: 10, + }, + ] + : []), + { + script_score: { + script: { + source: dedent(` + double jaccardSimilarity(Set a, Set b) { + if (a.size() == 0 || b.size() == 0) return 0.0; + Set intersection = new HashSet(a); + intersection.retainAll(b); + Set union = new HashSet(a); + union.addAll(b); + return (double) intersection.size() / union.size(); + } + Set tagsQuery = new HashSet(params.tags); + Set tagsDoc = new HashSet(doc.containsKey('tags.keyword') ? doc['tags.keyword'].values : []); + return jaccardSimilarity(tagsQuery, tagsDoc); + `), + params: { + tags, + }, + }, + }, + weight: 2, + }, + { + script_score: { + script: { + source: dedent(` + double jaccardSimilarity(Set a, Set b) { + if (a.size() == 0 || b.size() == 0) return 0.0; + Set intersection = new HashSet(a); + intersection.retainAll(b); + Set union = new HashSet(a); + union.addAll(b); + return (double) intersection.size() / union.size(); + } + Set instanceIdQuery = new HashSet(params.instanceId); + Set instanceIdDoc = new HashSet(); + if (doc.containsKey('kibana.alert.instance.id')) { + String instanceIdStr = doc['kibana.alert.instance.id'].value; + if (instanceIdStr != null && !instanceIdStr.isEmpty()) { + StringTokenizer tokenizer = new StringTokenizer(instanceIdStr, ','); + while (tokenizer.hasMoreTokens()) { + instanceIdDoc.add(tokenizer.nextToken()); + } + } + } + + return jaccardSimilarity(instanceIdQuery, instanceIdDoc); + `), + params: { + instanceId, + }, + }, + }, + weight: 5, + }, + ], + boost_mode: 'multiply', + }, + }, + ], + }, + }; +} diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_related_alerts_search.ts b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_related_alerts_search.ts new file mode 100644 index 0000000000000..de48bc18d49c9 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_related_alerts_search.ts @@ -0,0 +1,66 @@ +/* + * 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 { useSearchAlertsQuery } from '@kbn/alerts-ui-shared/src/common/hooks/use_search_alerts_query'; +import { getInspectResponse } from '@kbn/observability-shared-plugin/common'; +import { FETCH_STATUS, useInspectorContext } from '@kbn/observability-shared-plugin/public'; +import { useEffect } from 'react'; +import { useKibana } from '../../../../utils/kibana_react'; +import { TopAlert } from '../../../..'; +import { useBuildRelatedAlertsQuery } from './use_build_related_alerts_query'; +import { + OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES, + observabilityAlertFeatureIds, +} from '../../../../../common/constants'; +import { ObservabilityFields } from '../../../../../common/utils/alerting/types'; + +export const useRelatedAlertsSearch = ({ alert }: { alert: TopAlert }) => { + const { services } = useKibana(); + + const esQuery = useBuildRelatedAlertsQuery({ alert }); + + const addInspectorRequest = useInspectorContext().addInspectorRequest; + + const { data, isError, isFetching } = useSearchAlertsQuery({ + data: services.data, + ruleTypeIds: OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES, + consumers: observabilityAlertFeatureIds, + query: esQuery, + useDefaultContext: true, + pageSize: 100, + sort: [{ _score: { order: 'desc' } }], + }); + + useEffect(() => { + if (data?.querySnapshot && addInspectorRequest) { + const querySnapshot = data.querySnapshot; + addInspectorRequest({ + data: { + _inspect: [ + getInspectResponse({ + startTime: Date.now(), + esRequestParams: JSON.parse(querySnapshot.request?.[0]), + esResponse: JSON.parse(querySnapshot.response?.[0]), + esError: null, + esRequestStatus: 1, + operationName: 'SearchRelatedAlerts', + kibanaRequest: { + route: { + path: '/internal/search', + method: 'POST', + }, + } as any, + }), + ], + }, + status: FETCH_STATUS.SUCCESS, + }); + } + }, [addInspectorRequest, data]); + + return { data, isLoading: isFetching, isError }; +}; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/cases/components/cases.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/cases/components/cases.tsx index dabf4cb195230..971166b8c5569 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/cases/components/cases.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/cases/components/cases.tsx @@ -5,14 +5,15 @@ * 2.0. */ -import React, { Suspense, useState } from 'react'; +import React, { useState } from 'react'; import { CasesPermissions } from '@kbn/cases-plugin/common'; +import AlertsFlyout from '../../../components/alerts_flyout/alerts_flyout'; import { observabilityFeatureId } from '../../../../common'; import { useKibana } from '../../../utils/kibana_react'; import { usePluginContext } from '../../../hooks/use_plugin_context'; import { useFetchAlertDetail } from '../../../hooks/use_fetch_alert_detail'; import { useFetchAlertData } from '../../../hooks/use_fetch_alert_data'; -import { LazyAlertsFlyout, ObservabilityAlertsTable } from '../../..'; +import { ObservabilityAlertsTable } from '../../..'; import { CASES_PATH, paths } from '../../../../common/locators/paths'; export interface CasesProps { @@ -67,13 +68,11 @@ export function Cases({ permissions }: CasesProps) { /> {alertDetail && selectedAlertId !== '' && !alertLoading ? ( - - - + ) : null} ); diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx index bd1de2287517a..e841f75023780 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu.tsx @@ -12,10 +12,10 @@ import { } from '@kbn/deeplinks-observability'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; import { usePluginContext } from '../../../../hooks/use_plugin_context'; import { useKibana } from '../../../../utils/kibana_react'; -// FIXME: import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public' -import HeaderMenuPortal from './header_menu_portal'; +import { InspectorHeaderLink } from '../../../alert_details/components/inspector_header_link'; export function HeaderMenu(): React.ReactElement | null { const { share, theme, http } = useKibana().services; @@ -49,6 +49,7 @@ export function HeaderMenu(): React.ReactElement | null { defaultMessage: 'Annotations', })} + diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.test.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.test.tsx deleted file mode 100644 index 055c974dcf6db..0000000000000 --- a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.test.tsx +++ /dev/null @@ -1,30 +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 { render } from '@testing-library/react'; -import React from 'react'; -import HeaderMenuPortal from './header_menu_portal'; -import { themeServiceMock } from '@kbn/core/public/mocks'; - -describe('HeaderMenuPortal', () => { - describe('when unmounted', () => { - it('calls setHeaderActionMenu with undefined', () => { - const setHeaderActionMenu = jest.fn(); - const theme$ = themeServiceMock.createTheme$(); - - const { unmount } = render( - - test - - ); - - unmount(); - - expect(setHeaderActionMenu).toHaveBeenCalledWith(undefined); - }); - }); -}); diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.tsx deleted file mode 100644 index bdd14979f69b3..0000000000000 --- a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/header_menu/header_menu_portal.tsx +++ /dev/null @@ -1,43 +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, { ReactNode, useEffect, useMemo } from 'react'; -import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; -import { toMountPoint } from '@kbn/react-kibana-mount'; -import { AppMountParameters } from '@kbn/core/public'; -import { useKibana } from '../../../../utils/kibana_react'; -export interface HeaderMenuPortalProps { - children: ReactNode; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; - theme$: AppMountParameters['theme$']; -} - -// eslint-disable-next-line import/no-default-export -export default function HeaderMenuPortal({ - children, - setHeaderActionMenu, - theme$, -}: HeaderMenuPortalProps) { - const { i18n } = useKibana().services; - const portalNode = useMemo(() => createHtmlPortalNode(), []); - - useEffect(() => { - setHeaderActionMenu((element) => { - const mount = toMountPoint(, { - ...{ theme: { theme$ }, i18n }, - }); - return mount(element); - }); - - return () => { - portalNode.unmount(); - setHeaderActionMenu(undefined); - }; - }, [portalNode, setHeaderActionMenu, i18n, theme$]); - - return {children}; -} diff --git a/x-pack/solutions/observability/plugins/observability/public/plugin.ts b/x-pack/solutions/observability/plugins/observability/public/plugin.ts index 0adf05a94380a..41142cb07e1f7 100644 --- a/x-pack/solutions/observability/plugins/observability/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/observability/public/plugin.ts @@ -72,6 +72,7 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/ import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { StreamsPluginStart, StreamsPluginSetup } from '@kbn/streams-plugin/public'; import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; +import { Start as InspectorPluginStart } from '@kbn/inspector-plugin/public'; import { observabilityAppId, observabilityFeatureId } from '../common'; import { ALERTS_PATH, @@ -167,6 +168,7 @@ export interface ObservabilityPublicPluginsStart { investigate?: InvestigatePublicStart; streams?: StreamsPluginStart; fieldsMetadata: FieldsMetadataPublicStart; + inspector: InspectorPluginStart; } export type ObservabilityPublicStart = ReturnType; From 9c364ec18c8d756b67e4919bd78d40d9bf59f32f Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 24 Mar 2025 15:21:48 +0100 Subject: [PATCH 02/36] update types --- .../response-ops/alerts-table/components/alerts_flyout.tsx | 2 +- .../public/components/alert_actions/alert_actions.tsx | 4 +--- .../components/related_alerts/related_alerts_view.tsx | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_flyout.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_flyout.tsx index a660c26270cf1..813ae7e3df24a 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_flyout.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_flyout.tsx @@ -80,7 +80,7 @@ export const AlertsFlyout = ({ () => Header ? ( -
+ {...props} /> ) : null, [Header, props] diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index 6c5db666349c3..1f325fd1d927c 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -33,13 +33,11 @@ import { ALERT_DETAILS_PAGE_ID } from '../../pages/alert_details/alert_details'; export function AlertActions({ observabilityRuleTypeRegistry, alert, - id, tableId, refresh, openAlertInFlyout, }: Pick & { observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; - id: string; }) { const services = useKibana().services; @@ -53,7 +51,7 @@ export function AlertActions({ } = services.cases! as unknown as CasesPublicStart; // Cases is guaranteed to be defined in Observability const isSLODetailsPage = useRouteMatch(SLO_DETAIL_PATH); - const isInApp = Boolean(id === SLO_ALERTS_TABLE_ID && isSLODetailsPage); + const isInApp = Boolean(tableId === SLO_ALERTS_TABLE_ID && isSLODetailsPage); const userCasesPermissions = canUseCases([observabilityFeatureId]); const [viewInAppUrl, setViewInAppUrl] = useState(); diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx index 9ba905e4a002b..7905c099e5627 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx @@ -64,7 +64,6 @@ export function RelatedAlertsView({ alertData }: Props) { render: (item: Alert) => { return ( {}} observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} From 7f6b28a9f862dfa8542c4ebfbcb699a62ead8c26 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 24 Mar 2025 17:14:01 +0100 Subject: [PATCH 03/36] handle openc ase --- .../alert_actions/alert_actions.tsx | 2 +- .../alert_actions/use_case_actions.ts | 55 ++++++++----------- .../related_alerts/related_alerts_view.tsx | 15 ++++- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index 1f325fd1d927c..da1ba6e293399 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -85,7 +85,7 @@ export function AlertActions({ const { isPopoverOpen, setIsPopoverOpen, handleAddToExistingCaseClick, handleAddToNewCaseClick } = useCaseActions({ refresh, - alert, + alerts: [alert], }); const closeActionsPopover = useCallback(() => { diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/use_case_actions.ts b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/use_case_actions.ts index e8025881e91c4..b54643d655d64 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/use_case_actions.ts +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/use_case_actions.ts @@ -6,14 +6,13 @@ */ import { CaseAttachmentsWithoutOwner, CasesPublicStart } from '@kbn/cases-plugin/public'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useState } from 'react'; import { AttachmentType } from '@kbn/cases-plugin/common'; -import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import type { Alert } from '@kbn/alerting-types'; import type { EventNonEcsData } from '../../../common/typings'; import { useKibana } from '../../utils/kibana_react'; -export const useCaseActions = ({ alert, refresh }: { alert: Alert; refresh?: () => void }) => { +export const useCaseActions = ({ alerts, refresh }: { alerts: Alert[]; refresh?: () => void }) => { const services = useKibana().services; const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -27,47 +26,37 @@ export const useCaseActions = ({ alert, refresh }: { alert: Alert; refresh?: () }, [refresh]); const selectCaseModal = useCasesAddToExistingCaseModal({ onSuccess }); - const ecsData = useMemo( - () => ({ - _id: alert._id, - _index: alert._index, - }), - [alert._id, alert._index] - ); - const data = useMemo( - () => - Object.entries(alert ?? {}).reduce( - (acc, [field, value]) => [...acc, { field, value: value as string[] }], - [] - ), - [alert] - ); - - const caseAttachments: CaseAttachmentsWithoutOwner = useMemo(() => { - return ecsData?._id - ? [ - { - alertId: ecsData?._id ?? '', - index: ecsData?._index ?? '', - type: AttachmentType.alert, - rule: getRuleIdFromEvent({ ecs: ecsData, data: data ?? [] }), - }, - ] - : []; - }, [ecsData, getRuleIdFromEvent, data]); + function getCaseAttachments(): CaseAttachmentsWithoutOwner { + return alerts.map((alert) => ({ + alertId: alert?._id ?? '', + index: alert?._index ?? '', + type: AttachmentType.alert, + rule: getRuleIdFromEvent({ + ecs: { + _id: alert?._id ?? '', + _index: alert?._index ?? '', + }, + data: + Object.entries(alert ?? {}).reduce( + (acc, [field, value]) => [...acc, { field, value: value as string[] }], + [] + ) ?? [], + }), + })); + } const createCaseFlyout = useCasesAddToNewCaseFlyout({ onSuccess }); const closeActionsPopover = () => { setIsPopoverOpen(false); }; const handleAddToNewCaseClick = () => { - createCaseFlyout.open({ attachments: caseAttachments }); + createCaseFlyout.open({ attachments: getCaseAttachments() }); closeActionsPopover(); }; const handleAddToExistingCaseClick = () => { - selectCaseModal.open({ getAttachments: () => caseAttachments }); + selectCaseModal.open({ getAttachments: () => getCaseAttachments() }); closeActionsPopover(); }; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx index 7905c099e5627..ce37367b17988 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx @@ -34,6 +34,7 @@ import { import { intersection, isEmpty } from 'lodash'; import React, { useState } from 'react'; import AlertsFlyout from '@kbn/response-ops-alerts-table/components/alerts_flyout'; +import { useCaseActions } from '../../../../components/alert_actions/use_case_actions'; import AlertActions from '../../../../components/alert_actions/alert_actions'; import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; import { ObservabilityRuleTypeRegistry } from '../../../..'; @@ -54,12 +55,19 @@ export function RelatedAlertsView({ alertData }: Props) { const [flyoutAlert, setFlyoutAlert] = useState(null); const [flyoutAlertIndex, setFlyoutAlertIndex] = useState(0); const { observabilityRuleTypeRegistry } = usePluginContext(); + const [selectedAlerts, setSelectedAlerts] = useState([]); const services = useKibana().services; + const { handleAddToExistingCaseClick } = useCaseActions({ + alerts: selectedAlerts, + refresh: () => {}, + }); const columns: Array> = [ { - name: 'Actions', + name: i18n.translate('xpack.observability.relatedAlertsView.columns.actionsColumnTitle', { + defaultMessage: 'Actions', + }), width: '100px', render: (item: Alert) => { return ( @@ -169,7 +177,6 @@ export function RelatedAlertsView({ alertData }: Props) { }, ]; - const [selectedAlerts, setSelectedAlerts] = useState([]); const [pageIndex, setPageIndex] = useState(0); const onSelectionChange = (selected: Alert[]) => { setSelectedAlerts(selected); @@ -184,7 +191,9 @@ export function RelatedAlertsView({ alertData }: Props) { {}} + onClick={() => { + handleAddToExistingCaseClick(); + }} disabled={selectedAlerts.length === 0} > {i18n.translate('xpack.observability.relatedAlertsView.openCaseForSelectedButtonLabel', { From 76e89e4bed4b2148e5064bbc546ecf68c59cd24b Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Mar 2025 04:31:37 +0100 Subject: [PATCH 04/36] handle context --- .../mark_as_untracked_alert_action.tsx | 11 +++++++---- .../view_alert_details_alert_action.tsx | 17 +++++++---------- .../view_rule_details_alert_action.tsx | 15 ++++++--------- .../components/alert_actions/alert_actions.tsx | 1 + .../related_alerts/related_alerts.tsx | 11 ++++++++++- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx index 1b02d4c885f92..5e5864a5ee4c9 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx @@ -11,10 +11,12 @@ import React, { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiContextMenuItem } from '@elastic/eui'; import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { HttpStart } from '@kbn/core-http-browser'; +import type { NotificationsStart } from '@kbn/core-notifications-browser'; import type { AdditionalContext, AlertActionsProps } from '../types'; import { useBulkUntrackAlerts } from '../hooks/use_bulk_untrack_alerts'; import { typedMemo } from '../utils/react'; -import { useAlertsTableContext } from '../contexts/alerts_table_context'; /** * Alerts table row action to mark the selected alert as untracked @@ -25,9 +27,10 @@ export const MarkAsUntrackedAlertAction = typedMemo( refresh, onActionExecuted, }: Pick, 'alert' | 'refresh' | 'onActionExecuted'>) => { - const { - services: { http, notifications }, - } = useAlertsTableContext(); + const { http, notifications } = useKibana<{ + http: HttpStart; + notifications: NotificationsStart; + }>().services; const { mutateAsync: untrackAlerts } = useBulkUntrackAlerts({ http, notifications }); const isAlertActive = useMemo(() => alert[ALERT_STATUS]?.[0] === ALERT_STATUS_ACTIVE, [alert]); diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx index c45b38753c617..b89c56be9e106 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx @@ -11,9 +11,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiContextMenuItem } from '@elastic/eui'; import { ALERT_UUID } from '@kbn/rule-data-utils'; -import { useAlertsTableContext } from '../contexts/alerts_table_context'; -import type { AdditionalContext, AlertActionsProps } from '../types'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { HttpStart } from '@kbn/core-http-browser'; import { typedMemo } from '../utils/react'; +import type { AdditionalContext, AlertActionsProps } from '../types'; /** * Alerts table row action to open the selected alert detail page @@ -35,16 +36,12 @@ export const ViewAlertDetailsAlertAction = typedMemo( | 'resolveAlertPagePath' | 'tableId' >) => { - const { - services: { - http: { - basePath: { prepend }, - }, - }, - } = useAlertsTableContext(); + const { http } = useKibana<{ + http: HttpStart; + }>().services; const alertId = (alert[ALERT_UUID]?.[0] as string) ?? null; const pagePath = alertId && tableId && resolveAlertPagePath?.(alertId, tableId); - const linkToAlert = pagePath ? prepend(pagePath) : null; + const linkToAlert = pagePath ? http.basePath.prepend(pagePath) : null; if (isAlertDetailsEnabled && linkToAlert) { return ( diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx index 6b761bbba6632..6d29d10dddfc1 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx @@ -11,7 +11,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiContextMenuItem } from '@elastic/eui'; import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; -import { useAlertsTableContext } from '../contexts/alerts_table_context'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { HttpStart } from '@kbn/core-http-browser'; import type { AdditionalContext, AlertActionsProps } from '../types'; import { typedMemo } from '../utils/react'; @@ -24,16 +25,12 @@ export const ViewRuleDetailsAlertAction = typedMemo( resolveRulePagePath, tableId, }: Pick, 'alert' | 'resolveRulePagePath' | 'tableId'>) => { - const { - services: { - http: { - basePath: { prepend }, - }, - }, - } = useAlertsTableContext(); + const { http } = useKibana<{ + http: HttpStart; + }>().services; const ruleId = (alert[ALERT_RULE_UUID]?.[0] as string) ?? null; const pagePath = ruleId && tableId && resolveRulePagePath?.(ruleId, tableId); - const linkToRule = pagePath ? prepend(pagePath) : null; + const linkToRule = pagePath ? http.basePath.prepend(pagePath) : null; if (!linkToRule) { return null; diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index da1ba6e293399..de4a49fe1533a 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -124,6 +124,7 @@ export function AlertActions({ useMemo( () => ( ; } - return ; + return ( + + + + ); } From 3fa36d18cb46855d71a73c7d96e61bfee4d20b9f Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Mar 2025 13:01:05 +0100 Subject: [PATCH 05/36] wip --- .../alerts_table/common/cell_tooltip.tsx | 2 +- .../alerts_table/common/cell_value.tsx | 23 +- .../related_alerts/related_alerts.tsx | 13 +- .../related_alerts/related_alerts_table.tsx | 237 ++++++++++++++++++ 4 files changed, 268 insertions(+), 7 deletions(-) create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_tooltip.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_tooltip.tsx index 0298f1a3f1d47..d90c3e1805530 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_tooltip.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_tooltip.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiToolTip } from '@elastic/eui'; interface Props { - value: string; + value: React.ReactNode; tooltipContent: string; } diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx index 309350e5d5560..c09472f6bc8fa 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiLink } from '@elastic/eui'; +import { EuiButtonEmpty, EuiLink } from '@elastic/eui'; import React from 'react'; import { ALERT_DURATION, @@ -21,9 +21,11 @@ import { ALERT_RULE_CATEGORY, ALERT_START, ALERT_RULE_EXECUTION_TIMESTAMP, + ALERT_RULE_UUID, } from '@kbn/rule-data-utils'; import { isEmpty } from 'lodash'; import type { Alert } from '@kbn/alerting-types'; +import { paths } from '../../../../common/locators/paths'; import { asDuration } from '../../../../common/utils/formatters'; import { AlertSeverityBadge } from '../../alert_severity_badge'; import { AlertStatusIndicator } from '../../alert_status_indicator'; @@ -62,6 +64,7 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa alert, openAlertInFlyout, observabilityRuleTypeRegistry, + services: { http }, }) => { const value = getAlertFieldValue(alert, columnId); @@ -98,7 +101,23 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa ); case ALERT_RULE_NAME: const ruleCategory = getAlertFieldValue(alert, ALERT_RULE_CATEGORY); - return ; + const ruleId = getAlertFieldValue(alert, ALERT_RULE_UUID); + const ruleLink = ruleId ? http.basePath.prepend(paths.observability.ruleDetails(ruleId)) : ''; + return ( + + {value} + + } + tooltipContent={ruleCategory} + /> + ); default: return <>{value}; } diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx index db62489f6aed8..8080fa7b4f117 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx @@ -6,10 +6,11 @@ */ import React from 'react'; -import { EuiLoadingChart } from '@elastic/eui'; +import { EuiLoadingChart, EuiSpacer } from '@elastic/eui'; import { QueryClient } from '@tanstack/react-query'; import { QueryClientProvider } from '@tanstack/react-query'; import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; +import { RelatedAlertsTable } from './related_alerts_table'; import { RelatedAlertsView } from './related_alerts_view'; import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; @@ -25,8 +26,12 @@ export function RelatedAlerts({ alertData }: Props) { } return ( - - - + <> + + + + + + ); } diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx new file mode 100644 index 0000000000000..2e28738c3e4c7 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx @@ -0,0 +1,237 @@ +/* + * 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 { EuiCallOut, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import type { Alert } from '@kbn/alerting-types'; +import { i18n } from '@kbn/i18n'; +import { ALERT_START } from '@kbn/rule-data-utils'; +import React, { useState } from 'react'; +import { AlertsTable } from '@kbn/response-ops-alerts-table'; +import { SortOrder } from '@elastic/elasticsearch/lib/api/types'; +import { getColumns } from '../../../../components/alerts_table/common/get_columns'; +import { useCaseActions } from '../../../../components/alert_actions/use_case_actions'; +import AlertActions from '../../../../components/alert_actions/alert_actions'; +import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; +import { + GetObservabilityAlertsTableProp, + ObservabilityAlertsTableContext, + observabilityFeatureId, + ObservabilityPublicStart, +} from '../../../..'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; +import { useKibana } from '../../../../utils/kibana_react'; +import { AlertsFlyoutBody } from '../../../../components/alerts_flyout/alerts_flyout_body'; +import { AlertsFlyoutFooter } from '../../../../components/alerts_flyout/alerts_flyout_footer'; +import { OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES } from '../../../../../common/constants'; +import { AlertsTableCellValue } from '../../../../components/alerts_table/common/cell_value'; +import { casesFeatureId } from '../../../../../common'; + +interface Props { + alertData: AlertData; +} + +const columns = getColumns({ showRuleName: true }); +const initialSort = [ + { + [ALERT_START]: { + order: 'desc' as SortOrder, + }, + }, +]; + +const caseConfiguration: GetObservabilityAlertsTableProp<'casesConfiguration'> = { + featureId: casesFeatureId, + owner: [observabilityFeatureId], +}; + +export function RelatedAlertsTable({ alertData }: Props) { + const { formatted: alert } = alertData; + const { + data, + http, + notifications, + fieldFormats, + application, + licensing, + cases, + settings, + observability, + } = useKibana<{ observability: ObservabilityPublicStart }>().services; + // const { data, isLoading, isError } = useRelatedAlertsSearch({ alert }); + + const [flyoutAlert, setFlyoutAlert] = useState(null); + const [flyoutAlertIndex, setFlyoutAlertIndex] = useState(0); + const { observabilityRuleTypeRegistry, config } = usePluginContext(); + const [selectedAlerts, setSelectedAlerts] = useState([]); + + const services = useKibana().services; + const { handleAddToExistingCaseClick } = useCaseActions({ + alerts: selectedAlerts, + refresh: () => {}, + }); + + // const columns: Array> = [ + // { + // name: i18n.translate('xpack.observability.relatedAlertsView.columns.actionsColumnTitle', { + // defaultMessage: 'Actions', + // }), + // width: '100px', + // render: (item: Alert) => { + // return ( + // {}} + // observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} + // openAlertInFlyout={(alertId: string) => { + // setFlyoutAlert(item); + // const flyoutIndex = data?.alerts.findIndex((al) => al._id === item._id); + // setFlyoutAlertIndex(flyoutIndex ?? 0); + // }} + // /> + // ); + // }, + // }, + // { + // field: ALERT_STATUS, + // name: 'Status', + // render: (_, item: Alert) => { + // const value = getAlertFieldValue(item, ALERT_STATUS); + // if (value !== ALERT_STATUS_ACTIVE && value !== ALERT_STATUS_RECOVERED) { + // // NOTE: This should only be needed to narrow down the type. + // // Status should be either "active" or "recovered". + // return null; + // } + // return {value}; + // }, + // width: '150', + // }, + // { + // field: ALERT_RULE_NAME, + // name: 'Rule', + // truncateText: true, + // render: (_, item: Alert) => { + // const ruleName = getAlertFieldValue(item, ALERT_RULE_NAME); + // const ruleType = getAlertFieldValue(item, ALERT_RULE_CATEGORY); + // return ( + //
+ // { + // setFlyoutAlert(item); + // const flyoutIndex = data?.alerts.findIndex((al) => al._id === item._id); + // setFlyoutAlertIndex(flyoutIndex ?? 0); + // }} + // > + // {ruleName} + // + // {ruleType} + //
+ // ); + // }, + // }, + // { + // field: ALERT_INSTANCE_ID, + // name: 'Group', + // truncateText: true, + // render: (_, item: Alert) => { + // const instanceId = getAlertFieldValue(item, ALERT_INSTANCE_ID); + // return {instanceId}; + // }, + // }, + // { + // field: 'relation', + // name: i18n.translate('xpack.observability.relatedAlertsView.relation', { + // defaultMessage: 'Relation', + // }), + // render: (_, item: Alert) => { + // const instanceId = getAlertFieldValue(item, ALERT_INSTANCE_ID); + // const tags = getAlertFieldValue(item, ALERT_RULE_TAGS); + // const ruleUuid = getAlertFieldValue(item, ALERT_RULE_UUID); + // const hasSomeRelationWithInstance = + // intersection(alert.fields[ALERT_INSTANCE_ID].split(','), instanceId.split(',')).length > + // 0; + // const hasSomeRelationWithTags = + // intersection(alert.fields[ALERT_RULE_TAGS], tags.split(',')).length > 0; + // const hasRelationWithRule = ruleUuid === alert.fields[ALERT_RULE_UUID]; + // return ( + // <> + // {hasSomeRelationWithInstance && ( + // + // {i18n.translate('xpack.observability.columns.groupsBadgeLabel', { + // defaultMessage: 'Groups', + // })} + // + // )} + // {hasSomeRelationWithTags && ( + // + // {i18n.translate('xpack.observability.columns.tagsBadgeLabel', { + // defaultMessage: 'Tags', + // })} + // + // )} + // {hasRelationWithRule && ( + // + // {i18n.translate('xpack.observability.columns.ruleBadgeLabel', { + // defaultMessage: 'Rule', + // })} + // + // )} + // + // ); + // }, + // }, + // ]; + + return ( + + + +

+ {i18n.translate('xpack.observability.relatedAlertsView.p.weAreFetchingAlertsLabel', { + defaultMessage: + "We are fetching relevant alerts to the current alert based on some heuristics. Soon you'll be able to tweaks the weights applied to these heuristics", + })} +

+
+ + columns={columns} + ruleTypeIds={OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES} + initialSort={initialSort} + casesConfiguration={caseConfiguration} + additionalContext={{ + observabilityRuleTypeRegistry, + config, + }} + renderCellValue={AlertsTableCellValue} + renderActionsCell={AlertActions} + actionsColumnWidth={120} + renderFlyoutBody={AlertsFlyoutBody} + renderFlyoutFooter={AlertsFlyoutFooter} + showAlertStatusWithFlapping + services={{ + data, + http, + notifications, + fieldFormats, + application, + licensing, + cases, + settings, + }} + /> +
+ ); +} From c36cc910f9e602b42d7e04c00c0121e10acb02e2 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Mar 2025 17:57:13 +0100 Subject: [PATCH 06/36] handle table --- .../alert_actions/alert_actions.tsx | 45 +-- .../alerts_table/common/cell_value.tsx | 82 +++-- .../public/components/alerts_table/types.ts | 5 +- .../related_alerts/get_related_columns.tsx | 50 +++ .../related_alerts/related_alerts.tsx | 18 +- .../related_alerts/related_alerts_cell.tsx | 28 ++ .../related_alerts/related_alerts_table.tsx | 180 ++--------- .../related_alerts/related_alerts_view.tsx | 304 ------------------ .../related_alerts/relation_col.tsx | 63 ++++ .../use_related_alerts_search.ts | 66 ---- 10 files changed, 244 insertions(+), 597 deletions(-) create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_cell.tsx delete mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx delete mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_related_alerts_search.ts diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index de4a49fe1533a..6051751816489 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -27,18 +27,23 @@ import { useCaseActions } from './use_case_actions'; import { RULE_DETAILS_PAGE_ID } from '../../pages/rule_details/constants'; import { paths, SLO_DETAIL_PATH } from '../../../common/locators/paths'; import { parseAlert } from '../../pages/alerts/helpers/parse_alert'; -import { observabilityFeatureId, ObservabilityRuleTypeRegistry } from '../..'; +import { ObservabilityAlertsTableContext, observabilityFeatureId } from '../..'; import { ALERT_DETAILS_PAGE_ID } from '../../pages/alert_details/alert_details'; +export type ObsAlertActionProps = Pick< + AlertActionsProps, + 'alert' | 'openAlertInFlyout' | 'tableId' | 'refresh' +> & + ObservabilityAlertsTableContext; + export function AlertActions({ observabilityRuleTypeRegistry, alert, tableId, refresh, openAlertInFlyout, -}: Pick & { - observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; -}) { + parentAlert, +}: ObsAlertActionProps) { const services = useKibana().services; const { @@ -124,7 +129,6 @@ export function AlertActions({ useMemo( () => ( { const parsedAlert = parseAlert(observabilityRuleTypeRegistry)(alert); - openAlertInFlyout?.(parsedAlert.fields[ALERT_UUID]); }; + const hideViewInApp = isInApp || viewInAppUrl === '' || parentAlert; + return ( <> - - - - - - {viewInAppUrl !== '' && !isInApp ? ( + {!parentAlert && ( + + + + + + )} + {!hideViewInApp && ( - ) : null} + )} { +export const getAlertFieldValue = (alert: Alert, fieldName: string) => { // can be updated when working on https://github.com/elastic/kibana/issues/140819 const rawValue = alert[fieldName]; const value = Array.isArray(rawValue) ? rawValue.join() : rawValue; @@ -53,41 +54,52 @@ const getAlertFieldValue = (alert: Alert, fieldName: string) => { return '--'; }; +export type AlertCellRenderers = Record< + string, + (value: string, props: ObsAlertActionProps) => ReactNode +>; + /** * This implementation of `EuiDataGrid`'s `renderCellValue` * accepts `EuiDataGridCellValueElementProps`, plus `data` * from the TGrid */ // eslint-disable-next-line react/function-component-definition -export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellValue'> = ({ - columnId, - alert, - openAlertInFlyout, - observabilityRuleTypeRegistry, - services: { http }, -}) => { - const value = getAlertFieldValue(alert, columnId); +export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellValue'> = (props) => { + const { + columnId, + alert, + openAlertInFlyout, + observabilityRuleTypeRegistry, + services: { http }, + extraCellRenderers, + } = props; - switch (columnId) { - case ALERT_STATUS: + const cellRenderers: AlertCellRenderers = { + [ALERT_STATUS]: (value) => { if (value !== ALERT_STATUS_ACTIVE && value !== ALERT_STATUS_RECOVERED) { // NOTE: This should only be needed to narrow down the type. // Status should be either "active" or "recovered". return null; } return ; - case TIMESTAMP: - case ALERT_START: - case ALERT_RULE_EXECUTION_TIMESTAMP: - return ; - case ALERT_DURATION: - return <>{asDuration(Number(value))}; - case ALERT_SEVERITY: - return ; - case ALERT_EVALUATION_VALUE: + }, + [TIMESTAMP]: (value) => ( + + ), + [ALERT_START]: (value) => ( + + ), + [ALERT_RULE_EXECUTION_TIMESTAMP]: (value) => ( + + ), + [ALERT_DURATION]: (value) => <>{asDuration(Number(value))}, + [ALERT_SEVERITY]: (value) => , + [ALERT_EVALUATION_VALUE]: (value) => { const multipleValues = getAlertFieldValue(alert, ALERT_EVALUATION_VALUES); return <>{multipleValues ?? value}; - case ALERT_REASON: + }, + [ALERT_REASON]: (value) => { if (!observabilityRuleTypeRegistry) return <>{value}; const parsedAlert = parseAlert(observabilityRuleTypeRegistry)(alert); return ( @@ -99,26 +111,26 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa {parsedAlert.reason} ); - case ALERT_RULE_NAME: + }, + [ALERT_RULE_NAME]: (value) => { const ruleCategory = getAlertFieldValue(alert, ALERT_RULE_CATEGORY); const ruleId = getAlertFieldValue(alert, ALERT_RULE_UUID); const ruleLink = ruleId ? http.basePath.prepend(paths.observability.ruleDetails(ruleId)) : ''; return ( + {value} - + } tooltipContent={ruleCategory} /> ); - default: - return <>{value}; - } + }, + ...(extraCellRenderers ?? {}), + }; + + const val = getAlertFieldValue(alert, columnId); + + return cellRenderers[columnId] ? cellRenderers[columnId](val, props) : <>{val}; }; diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/types.ts b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/types.ts index 382dacb163af5..dd03ca4919bfd 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/types.ts +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/types.ts @@ -7,11 +7,14 @@ import { SetOptional } from 'type-fest'; import type { AlertsTablePropsWithRef } from '@kbn/response-ops-alerts-table/types'; -import type { ConfigSchema, ObservabilityRuleTypeRegistry } from '../..'; +import type { ConfigSchema, ObservabilityRuleTypeRegistry, TopAlert } from '../..'; +import { AlertCellRenderers } from './common/cell_value'; export interface ObservabilityAlertsTableContext { observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; config: ConfigSchema; + extraCellRenderers?: AlertCellRenderers; + parentAlert?: TopAlert; } export type ObservabilityAlertsTableProps = SetOptional< diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx new file mode 100644 index 0000000000000..fef3d3f08aef3 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx @@ -0,0 +1,50 @@ +/* + * 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 type { EuiDataGridColumn } from '@elastic/eui'; +import { ALERT_RULE_NAME, ALERT_STATUS, ALERT_REASON } from '@kbn/rule-data-utils'; +import { i18n } from '@kbn/i18n'; +import { RELATED_ACTIONS_COL, RELATION_COL } from './related_alerts_cell'; + +export const getRelatedColumns = (): EuiDataGridColumn[] => { + return [ + { + displayAsText: i18n.translate('xpack.observability.alertsTGrid.statusColumnDescription', { + defaultMessage: 'Alert Status', + }), + id: ALERT_STATUS, + initialWidth: 120, + }, + { + displayAsText: i18n.translate('xpack.observability.alertsTGrid.ruleNameColumnDescription', { + defaultMessage: 'Rule name', + }), + id: ALERT_RULE_NAME, + initialWidth: 250, + }, + { + displayAsText: i18n.translate('xpack.observability.alertsTGrid.reasonDescription', { + defaultMessage: 'Reason', + }), + id: ALERT_REASON, + initialWidth: 300, + }, + { + displayAsText: i18n.translate('xpack.observability.alertsTGrid.relationColumnDescription', { + defaultMessage: 'Relation', + }), + id: RELATION_COL, + }, + { + displayAsText: i18n.translate('xpack.observability.alertsTGrid.actionsColumnDescription', { + defaultMessage: 'Actions', + }), + id: RELATED_ACTIONS_COL, + initialWidth: 150, + }, + ]; +}; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx index 8080fa7b4f117..ecb0213c15948 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts.tsx @@ -6,12 +6,8 @@ */ import React from 'react'; -import { EuiLoadingChart, EuiSpacer } from '@elastic/eui'; -import { QueryClient } from '@tanstack/react-query'; -import { QueryClientProvider } from '@tanstack/react-query'; -import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; +import { EuiLoadingChart } from '@elastic/eui'; import { RelatedAlertsTable } from './related_alerts_table'; -import { RelatedAlertsView } from './related_alerts_view'; import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; interface Props { @@ -19,19 +15,9 @@ interface Props { } export function RelatedAlerts({ alertData }: Props) { - const queryClient = new QueryClient(); - if (!alertData) { return ; } - return ( - <> - - - - - - - ); + return ; } diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_cell.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_cell.tsx new file mode 100644 index 0000000000000..f8251ff27e3ed --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_cell.tsx @@ -0,0 +1,28 @@ +/* + * 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 from 'react'; +import { ALERT_REASON } from '@kbn/rule-data-utils'; +import { EuiText } from '@elastic/eui'; +import AlertActions from '../../../../components/alert_actions/alert_actions'; +import { RelationCol } from './relation_col'; +import { AlertCellRenderers } from '../../../../components/alerts_table/common/cell_value'; + +export const RELATION_COL = 'relation'; +export const RELATED_ACTIONS_COL = 'relatedActions'; + +export const relatedAlertsRowRenderer: AlertCellRenderers = { + [RELATION_COL]: (value, props) => { + return ; + }, + [ALERT_REASON]: (value) => { + return {value}; + }, + [RELATED_ACTIONS_COL]: (val, props) => { + return ; + }, +}; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx index 2e28738c3e4c7..44bf9332180df 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx @@ -6,21 +6,18 @@ */ import { EuiCallOut, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; -import type { Alert } from '@kbn/alerting-types'; import { i18n } from '@kbn/i18n'; -import { ALERT_START } from '@kbn/rule-data-utils'; -import React, { useState } from 'react'; +import React from 'react'; import { AlertsTable } from '@kbn/response-ops-alerts-table'; import { SortOrder } from '@elastic/elasticsearch/lib/api/types'; -import { getColumns } from '../../../../components/alerts_table/common/get_columns'; -import { useCaseActions } from '../../../../components/alert_actions/use_case_actions'; -import AlertActions from '../../../../components/alert_actions/alert_actions'; +import { relatedAlertsRowRenderer } from './related_alerts_cell'; +import { getRelatedColumns } from './get_related_columns'; +import { useBuildRelatedAlertsQuery } from '../../hooks/related_alerts/use_build_related_alerts_query'; import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; import { GetObservabilityAlertsTableProp, ObservabilityAlertsTableContext, observabilityFeatureId, - ObservabilityPublicStart, } from '../../../..'; import { usePluginContext } from '../../../../hooks/use_plugin_context'; import { useKibana } from '../../../../utils/kibana_react'; @@ -28,165 +25,34 @@ import { AlertsFlyoutBody } from '../../../../components/alerts_flyout/alerts_fl import { AlertsFlyoutFooter } from '../../../../components/alerts_flyout/alerts_flyout_footer'; import { OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES } from '../../../../../common/constants'; import { AlertsTableCellValue } from '../../../../components/alerts_table/common/cell_value'; -import { casesFeatureId } from '../../../../../common'; +import { casesFeatureIdV2 } from '../../../../../common'; interface Props { alertData: AlertData; } -const columns = getColumns({ showRuleName: true }); +const columns = getRelatedColumns(); const initialSort = [ { - [ALERT_START]: { + ['_score']: { order: 'desc' as SortOrder, }, }, ]; const caseConfiguration: GetObservabilityAlertsTableProp<'casesConfiguration'> = { - featureId: casesFeatureId, + featureId: casesFeatureIdV2, owner: [observabilityFeatureId], }; +const RELATED_ALERTS_TABLE_ID = 'xpack.observability.alerts.relatedAlerts'; + export function RelatedAlertsTable({ alertData }: Props) { const { formatted: alert } = alertData; - const { - data, - http, - notifications, - fieldFormats, - application, - licensing, - cases, - settings, - observability, - } = useKibana<{ observability: ObservabilityPublicStart }>().services; - // const { data, isLoading, isError } = useRelatedAlertsSearch({ alert }); - - const [flyoutAlert, setFlyoutAlert] = useState(null); - const [flyoutAlertIndex, setFlyoutAlertIndex] = useState(0); + const esQuery = useBuildRelatedAlertsQuery({ alert }); const { observabilityRuleTypeRegistry, config } = usePluginContext(); - const [selectedAlerts, setSelectedAlerts] = useState([]); const services = useKibana().services; - const { handleAddToExistingCaseClick } = useCaseActions({ - alerts: selectedAlerts, - refresh: () => {}, - }); - - // const columns: Array> = [ - // { - // name: i18n.translate('xpack.observability.relatedAlertsView.columns.actionsColumnTitle', { - // defaultMessage: 'Actions', - // }), - // width: '100px', - // render: (item: Alert) => { - // return ( - // {}} - // observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} - // openAlertInFlyout={(alertId: string) => { - // setFlyoutAlert(item); - // const flyoutIndex = data?.alerts.findIndex((al) => al._id === item._id); - // setFlyoutAlertIndex(flyoutIndex ?? 0); - // }} - // /> - // ); - // }, - // }, - // { - // field: ALERT_STATUS, - // name: 'Status', - // render: (_, item: Alert) => { - // const value = getAlertFieldValue(item, ALERT_STATUS); - // if (value !== ALERT_STATUS_ACTIVE && value !== ALERT_STATUS_RECOVERED) { - // // NOTE: This should only be needed to narrow down the type. - // // Status should be either "active" or "recovered". - // return null; - // } - // return {value}; - // }, - // width: '150', - // }, - // { - // field: ALERT_RULE_NAME, - // name: 'Rule', - // truncateText: true, - // render: (_, item: Alert) => { - // const ruleName = getAlertFieldValue(item, ALERT_RULE_NAME); - // const ruleType = getAlertFieldValue(item, ALERT_RULE_CATEGORY); - // return ( - //
- // { - // setFlyoutAlert(item); - // const flyoutIndex = data?.alerts.findIndex((al) => al._id === item._id); - // setFlyoutAlertIndex(flyoutIndex ?? 0); - // }} - // > - // {ruleName} - // - // {ruleType} - //
- // ); - // }, - // }, - // { - // field: ALERT_INSTANCE_ID, - // name: 'Group', - // truncateText: true, - // render: (_, item: Alert) => { - // const instanceId = getAlertFieldValue(item, ALERT_INSTANCE_ID); - // return {instanceId}; - // }, - // }, - // { - // field: 'relation', - // name: i18n.translate('xpack.observability.relatedAlertsView.relation', { - // defaultMessage: 'Relation', - // }), - // render: (_, item: Alert) => { - // const instanceId = getAlertFieldValue(item, ALERT_INSTANCE_ID); - // const tags = getAlertFieldValue(item, ALERT_RULE_TAGS); - // const ruleUuid = getAlertFieldValue(item, ALERT_RULE_UUID); - // const hasSomeRelationWithInstance = - // intersection(alert.fields[ALERT_INSTANCE_ID].split(','), instanceId.split(',')).length > - // 0; - // const hasSomeRelationWithTags = - // intersection(alert.fields[ALERT_RULE_TAGS], tags.split(',')).length > 0; - // const hasRelationWithRule = ruleUuid === alert.fields[ALERT_RULE_UUID]; - // return ( - // <> - // {hasSomeRelationWithInstance && ( - // - // {i18n.translate('xpack.observability.columns.groupsBadgeLabel', { - // defaultMessage: 'Groups', - // })} - // - // )} - // {hasSomeRelationWithTags && ( - // - // {i18n.translate('xpack.observability.columns.tagsBadgeLabel', { - // defaultMessage: 'Tags', - // })} - // - // )} - // {hasRelationWithRule && ( - // - // {i18n.translate('xpack.observability.columns.ruleBadgeLabel', { - // defaultMessage: 'Rule', - // })} - // - // )} - // - // ); - // }, - // }, - // ]; return ( @@ -207,6 +73,8 @@ export function RelatedAlertsTable({ alertData }: Props) {

+ id={RELATED_ALERTS_TABLE_ID} + query={esQuery} columns={columns} ruleTypeIds={OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES} initialSort={initialSort} @@ -214,22 +82,22 @@ export function RelatedAlertsTable({ alertData }: Props) { additionalContext={{ observabilityRuleTypeRegistry, config, + extraCellRenderers: relatedAlertsRowRenderer, + parentAlert: alert, }} renderCellValue={AlertsTableCellValue} - renderActionsCell={AlertActions} - actionsColumnWidth={120} renderFlyoutBody={AlertsFlyoutBody} renderFlyoutFooter={AlertsFlyoutFooter} showAlertStatusWithFlapping - services={{ - data, - http, - notifications, - fieldFormats, - application, - licensing, - cases, - settings, + services={services} + gridStyle={{ + border: 'horizontal', + header: 'underline', + cellPadding: 'l', + fontSize: 'm', + }} + rowHeightsOptions={{ + defaultHeight: 'auto', }} />
diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx deleted file mode 100644 index ce37367b17988..0000000000000 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_view.tsx +++ /dev/null @@ -1,304 +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 { - EuiBadge, - EuiInMemoryTable, - EuiBasicTableColumn, - EuiButton, - EuiCallOut, - EuiFlexGroup, - EuiTableSelectionType, - EuiText, - EuiSearchBarProps, - EuiSpacer, - EuiButtonEmpty, -} from '@elastic/eui'; -import type { Alert } from '@kbn/alerting-types'; -import { i18n } from '@kbn/i18n'; -import { - ALERT_INSTANCE_ID, - ALERT_REASON, - ALERT_RULE_CATEGORY, - ALERT_RULE_NAME, - ALERT_RULE_TAGS, - ALERT_RULE_UUID, - ALERT_STATUS, - ALERT_STATUS_ACTIVE, - ALERT_STATUS_RECOVERED, -} from '@kbn/rule-data-utils'; -import { intersection, isEmpty } from 'lodash'; -import React, { useState } from 'react'; -import AlertsFlyout from '@kbn/response-ops-alerts-table/components/alerts_flyout'; -import { useCaseActions } from '../../../../components/alert_actions/use_case_actions'; -import AlertActions from '../../../../components/alert_actions/alert_actions'; -import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; -import { ObservabilityRuleTypeRegistry } from '../../../..'; -import { usePluginContext } from '../../../../hooks/use_plugin_context'; -import { useKibana } from '../../../../utils/kibana_react'; -import { useRelatedAlertsSearch } from '../../hooks/related_alerts/use_related_alerts_search'; -import { AlertsFlyoutBody } from '../../../../components/alerts_flyout/alerts_flyout_body'; -import { AlertsFlyoutFooter } from '../../../../components/alerts_flyout/alerts_flyout_footer'; - -interface Props { - alertData: AlertData; -} - -export function RelatedAlertsView({ alertData }: Props) { - const { formatted: alert } = alertData; - const { data, isLoading, isError } = useRelatedAlertsSearch({ alert }); - - const [flyoutAlert, setFlyoutAlert] = useState(null); - const [flyoutAlertIndex, setFlyoutAlertIndex] = useState(0); - const { observabilityRuleTypeRegistry } = usePluginContext(); - const [selectedAlerts, setSelectedAlerts] = useState([]); - - const services = useKibana().services; - const { handleAddToExistingCaseClick } = useCaseActions({ - alerts: selectedAlerts, - refresh: () => {}, - }); - - const columns: Array> = [ - { - name: i18n.translate('xpack.observability.relatedAlertsView.columns.actionsColumnTitle', { - defaultMessage: 'Actions', - }), - width: '100px', - render: (item: Alert) => { - return ( - {}} - observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} - openAlertInFlyout={(alertId: string) => { - setFlyoutAlert(item); - const flyoutIndex = data?.alerts.findIndex((al) => al._id === item._id); - setFlyoutAlertIndex(flyoutIndex ?? 0); - }} - /> - ); - }, - }, - { - field: ALERT_STATUS, - name: 'Status', - render: (_, item: Alert) => { - const value = getAlertFieldValue(item, ALERT_STATUS); - if (value !== ALERT_STATUS_ACTIVE && value !== ALERT_STATUS_RECOVERED) { - // NOTE: This should only be needed to narrow down the type. - // Status should be either "active" or "recovered". - return null; - } - return {value}; - }, - width: '150', - }, - { - field: ALERT_RULE_NAME, - name: 'Rule', - truncateText: true, - render: (_, item: Alert) => { - const ruleName = getAlertFieldValue(item, ALERT_RULE_NAME); - const ruleType = getAlertFieldValue(item, ALERT_RULE_CATEGORY); - return ( -
- { - setFlyoutAlert(item); - const flyoutIndex = data?.alerts.findIndex((al) => al._id === item._id); - setFlyoutAlertIndex(flyoutIndex ?? 0); - }} - > - {ruleName} - - {ruleType} -
- ); - }, - }, - { - field: ALERT_INSTANCE_ID, - name: 'Group', - truncateText: true, - render: (_, item: Alert) => { - const instanceId = getAlertFieldValue(item, ALERT_INSTANCE_ID); - return {instanceId}; - }, - }, - { - field: 'relation', - name: i18n.translate('xpack.observability.relatedAlertsView.relation', { - defaultMessage: 'Relation', - }), - render: (_, item: Alert) => { - const instanceId = getAlertFieldValue(item, ALERT_INSTANCE_ID); - const tags = getAlertFieldValue(item, ALERT_RULE_TAGS); - const ruleUuid = getAlertFieldValue(item, ALERT_RULE_UUID); - const hasSomeRelationWithInstance = - intersection(alert.fields[ALERT_INSTANCE_ID].split(','), instanceId.split(',')).length > - 0; - const hasSomeRelationWithTags = - intersection(alert.fields[ALERT_RULE_TAGS], tags.split(',')).length > 0; - const hasRelationWithRule = ruleUuid === alert.fields[ALERT_RULE_UUID]; - return ( - <> - {hasSomeRelationWithInstance && ( - - {i18n.translate('xpack.observability.columns.groupsBadgeLabel', { - defaultMessage: 'Groups', - })} - - )} - {hasSomeRelationWithTags && ( - - {i18n.translate('xpack.observability.columns.tagsBadgeLabel', { - defaultMessage: 'Tags', - })} - - )} - {hasRelationWithRule && ( - - {i18n.translate('xpack.observability.columns.ruleBadgeLabel', { - defaultMessage: 'Rule', - })} - - )} - - ); - }, - }, - ]; - - const [pageIndex, setPageIndex] = useState(0); - const onSelectionChange = (selected: Alert[]) => { - setSelectedAlerts(selected); - }; - const selection: EuiTableSelectionType = { - onSelectionChange, - selectable: () => true, - }; - - const renderToolsRight = () => { - return [ - { - handleAddToExistingCaseClick(); - }} - disabled={selectedAlerts.length === 0} - > - {i18n.translate('xpack.observability.relatedAlertsView.openCaseForSelectedButtonLabel', { - defaultMessage: 'Open case for {count} selected alerts', - values: { - count: selectedAlerts.length, - }, - })} - , - ]; - }; - - const search: EuiSearchBarProps = { - toolsLeft: renderToolsRight(), - box: { - incremental: true, - }, - filters: [], - }; - - return ( - - - -

- {i18n.translate('xpack.observability.relatedAlertsView.p.weAreFetchingAlertsLabel', { - defaultMessage: - "We are fetching relevant alerts to the current alert based on some heuristics. Soon you'll be able to tweaks the weights applied to these heuristics", - })} -

-
- - itemId="_id" - search={search} - tableCaption={MOST_RELEVANT_ALERTS} - items={data?.alerts ?? []} - rowHeader={ALERT_REASON} - columns={columns} - loading={isLoading} - error={isError ? ERROR_FETCHING : undefined} - selection={selection} - tableLayout="auto" - pagination={true} - /> - {flyoutAlert && ( - - flyoutIndex={flyoutAlertIndex} - onClose={() => setFlyoutAlert(null)} - isLoading={isLoading} - alert={flyoutAlert} - alerts={data?.alerts ?? []} - onPaginate={(page) => { - setPageIndex(page); - setFlyoutAlertIndex(page); - setFlyoutAlert(data?.alerts[page] ?? null); - }} - isLoadingAlerts={isLoading} - refresh={() => {}} - alertsCount={data?.alerts.length ?? 0} - isLoadingMutedAlerts={false} - isLoadingCases={false} - isLoadingMaintenanceWindows={false} - pageIndex={pageIndex} - pageSize={0} - services={services} - columns={[]} - renderFlyoutBody={AlertsFlyoutBody} - renderFlyoutFooter={AlertsFlyoutFooter} - observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} - /> - )} -
- ); -} - -const MOST_RELEVANT_ALERTS = i18n.translate('xpack.observability.relatedAlertsView.mostRelevant', { - defaultMessage: 'Most relevant alerts to the current one', -}); - -const ERROR_FETCHING = i18n.translate('xpack.observability.relatedAlertsView.error', { - defaultMessage: 'Error fetching relevant alerts', -}); - -export const getAlertFieldValue = (alert: Alert, fieldName: string) => { - // can be updated when working on https://github.com/elastic/kibana/issues/140819 - const rawValue = alert[fieldName]; - const value = Array.isArray(rawValue) ? rawValue.join() : rawValue; - - if (!isEmpty(value)) { - if (typeof value === 'object') { - try { - return JSON.stringify(value); - } catch (e) { - return 'Error: Unable to parse JSON value.'; - } - } - return value; - } - - return '--'; -}; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx new file mode 100644 index 0000000000000..f2ab0519e9da4 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx @@ -0,0 +1,63 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ALERT_INSTANCE_ID, ALERT_RULE_TAGS, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { intersection } from 'lodash'; +import { EuiDescriptionList } from '@elastic/eui'; +import type { Alert } from '@kbn/alerting-types'; +import { TagsList } from '@kbn/observability-shared-plugin/public'; +import type { TopAlert } from '../../../..'; +import { getAlertFieldValue } from '../../../../components/alerts_table/common/cell_value'; + +export function RelationCol({ alert, parentAlert }: { alert: Alert; parentAlert: TopAlert }) { + const instanceId = getAlertFieldValue(alert, ALERT_INSTANCE_ID); + const tags = getAlertFieldValue(alert, ALERT_RULE_TAGS); + const ruleUuid = getAlertFieldValue(alert, ALERT_RULE_UUID); + const hasSomeRelationWithInstance = + intersection(parentAlert.fields[ALERT_INSTANCE_ID].split(','), instanceId.split(',')).length > + 0; + const hasSomeRelationWithTags = + intersection(parentAlert.fields[ALERT_RULE_TAGS], tags.split(',')).length > 0; + const hasRelationWithRule = ruleUuid === parentAlert.fields[ALERT_RULE_UUID]; + const relations = []; + if (hasSomeRelationWithInstance) { + relations.push({ + title: i18n.translate('xpack.observability.columns.groupsBadgeLabel', { + defaultMessage: 'Groups', + }), + description: instanceId, + }); + } + if (hasSomeRelationWithTags) { + relations.push({ + title: i18n.translate('xpack.observability.columns.tagsBadgeLabel', { + defaultMessage: 'Tags', + }), + description: ( + + ), + }); + } + if (hasRelationWithRule) { + relations.push({ + title: i18n.translate('xpack.observability.columns.ruleBadgeLabel', { + defaultMessage: 'Rule', + }), + description: ruleUuid, + }); + } + return ( + + ); +} diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_related_alerts_search.ts b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_related_alerts_search.ts deleted file mode 100644 index de48bc18d49c9..0000000000000 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_related_alerts_search.ts +++ /dev/null @@ -1,66 +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 { useSearchAlertsQuery } from '@kbn/alerts-ui-shared/src/common/hooks/use_search_alerts_query'; -import { getInspectResponse } from '@kbn/observability-shared-plugin/common'; -import { FETCH_STATUS, useInspectorContext } from '@kbn/observability-shared-plugin/public'; -import { useEffect } from 'react'; -import { useKibana } from '../../../../utils/kibana_react'; -import { TopAlert } from '../../../..'; -import { useBuildRelatedAlertsQuery } from './use_build_related_alerts_query'; -import { - OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES, - observabilityAlertFeatureIds, -} from '../../../../../common/constants'; -import { ObservabilityFields } from '../../../../../common/utils/alerting/types'; - -export const useRelatedAlertsSearch = ({ alert }: { alert: TopAlert }) => { - const { services } = useKibana(); - - const esQuery = useBuildRelatedAlertsQuery({ alert }); - - const addInspectorRequest = useInspectorContext().addInspectorRequest; - - const { data, isError, isFetching } = useSearchAlertsQuery({ - data: services.data, - ruleTypeIds: OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES, - consumers: observabilityAlertFeatureIds, - query: esQuery, - useDefaultContext: true, - pageSize: 100, - sort: [{ _score: { order: 'desc' } }], - }); - - useEffect(() => { - if (data?.querySnapshot && addInspectorRequest) { - const querySnapshot = data.querySnapshot; - addInspectorRequest({ - data: { - _inspect: [ - getInspectResponse({ - startTime: Date.now(), - esRequestParams: JSON.parse(querySnapshot.request?.[0]), - esResponse: JSON.parse(querySnapshot.response?.[0]), - esError: null, - esRequestStatus: 1, - operationName: 'SearchRelatedAlerts', - kibanaRequest: { - route: { - path: '/internal/search', - method: 'POST', - }, - } as any, - }), - ], - }, - status: FETCH_STATUS.SUCCESS, - }); - } - }, [addInspectorRequest, data]); - - return { data, isLoading: isFetching, isError }; -}; From 43d9cf8eb7df363bd3061074f9b5b1f4be24beba Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Mar 2025 18:04:35 +0100 Subject: [PATCH 07/36] fix types --- .../src/common/apis/search_alerts/search_alerts.ts | 4 ---- .../src/common/hooks/use_search_alerts_query.ts | 8 ++------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts index ef8d0e7b96262..55c8e2fb3e351 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts @@ -68,10 +68,6 @@ export interface SearchAlertsParams { * The page size to fetch */ pageSize: number; - /** - * Force using the default context, otherwise use the AlertQueryContext - */ - useDefaultContext?: boolean; } export interface SearchAlertsResult { diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts index df1e629e7ed59..4450afa788c39 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts @@ -27,11 +27,7 @@ export const queryKeyPrefix = ['alerts', searchAlerts.name]; * When testing components that depend on this hook, prefer mocking the {@link searchAlerts} function instead of the hook itself. * @external https://tanstack.com/query/v4/docs/framework/react/guides/testing */ -export const useSearchAlertsQuery = ({ - data, - useDefaultContext, - ...params -}: UseSearchAlertsQueryParams) => { +export const useSearchAlertsQuery = ({ data, ...params }: UseSearchAlertsQueryParams) => { const { ruleTypeIds, consumers, @@ -64,7 +60,7 @@ export const useSearchAlertsQuery = ({ pageSize, }), refetchOnWindowFocus: false, - context: useDefaultContext === true ? undefined : AlertsQueryContext, + context: AlertsQueryContext, enabled: ruleTypeIds.length > 0, // To avoid flash of empty state with pagination, see https://tanstack.com/query/latest/docs/framework/react/guides/paginated-queries#better-paginated-queries-with-placeholderdata keepPreviousData: true, From 55366c28723c982aca5fae4de2973a434187d2cc Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Mar 2025 18:07:02 +0100 Subject: [PATCH 08/36] revert --- .../components/alerts_data_grid.tsx | 28 ++++---- .../alerts-table/components/alerts_flyout.tsx | 10 +-- .../components/default_alert_actions.tsx | 66 ++++--------------- .../mark_as_untracked_alert_action.tsx | 13 ++-- .../components/mute_alert_action.tsx | 19 +++--- .../view_alert_details_alert_action.tsx | 27 ++++---- .../view_rule_details_alert_action.tsx | 17 +++-- 7 files changed, 64 insertions(+), 116 deletions(-) diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_data_grid.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_data_grid.tsx index ba8e5172a7031..fce5457d926c8 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_data_grid.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_data_grid.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { FC, useCallback, useMemo } from 'react'; +import React, { FC, lazy, Suspense, useCallback, useMemo } from 'react'; import { EuiDataGrid, EuiDataGridControlColumn, @@ -28,12 +28,14 @@ import { AdditionalContext, AlertsDataGridProps, CellActionsOptions } from '../t import { useGetToolbarVisibility } from '../hooks/use_toolbar_visibility'; import { InspectButtonContainer } from './alerts_query_inspector'; import { typedMemo } from '../utils/react'; -import { AlertsFlyout } from './alerts_flyout'; +import type { AlertsFlyout as AlertsFlyoutType } from './alerts_flyout'; import { useBulkActions } from '../hooks/use_bulk_actions'; import { useSorting } from '../hooks/use_sorting'; import { CellPopoverHost } from './cell_popover_host'; import { NonVirtualizedGridBody } from './non_virtualized_grid_body'; +const AlertsFlyout = lazy(() => import('./alerts_flyout')) as typeof AlertsFlyoutType; + const defaultGridStyle: EuiDataGridStyle = { border: 'none', header: 'underline', @@ -334,16 +336,18 @@ export const AlertsDataGrid = typedMemo( return (
- {flyoutAlertIndex > -1 && ( - - {...renderContext} - alert={alerts[flyoutAlertIndex]} - alertsCount={alertsCount} - onClose={handleFlyoutClose} - flyoutIndex={flyoutAlertIndex + pageIndex * pageSize} - onPaginate={onPaginateFlyout} - /> - )} + + {flyoutAlertIndex > -1 && ( + + {...renderContext} + alert={alerts[flyoutAlertIndex]} + alertsCount={alertsCount} + onClose={handleFlyoutClose} + flyoutIndex={flyoutAlertIndex + pageIndex * pageSize} + onPaginate={onPaginateFlyout} + /> + )} + {alertsCount > 0 && ( ({ alert, ...renderContext -}: Omit< - RenderContext, - | 'oldAlertsData' - | 'ecsAlertsData' - | 'dataGridRef' - | 'browserFields' - | 'bulkActionsStore' - | 'openAlertInFlyout' -> & { +}: RenderContext & { alert: Alert; flyoutIndex: number; isLoading: boolean; diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/default_alert_actions.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/default_alert_actions.tsx index 9d7c7de3142be..7317fdff1ff18 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/default_alert_actions.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/default_alert_actions.tsx @@ -10,78 +10,38 @@ import React from 'react'; import { useLoadRuleTypesQuery } from '@kbn/alerts-ui-shared/src/common/hooks'; import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { HttpStart } from '@kbn/core-http-browser'; -import type { NotificationsStart } from '@kbn/core-notifications-browser'; import { ViewRuleDetailsAlertAction } from './view_rule_details_alert_action'; import type { AdditionalContext, AlertActionsProps } from '../types'; import { ViewAlertDetailsAlertAction } from './view_alert_details_alert_action'; import { MuteAlertAction } from './mute_alert_action'; import { MarkAsUntrackedAlertAction } from './mark_as_untracked_alert_action'; +import { useAlertsTableContext } from '../contexts/alerts_table_context'; /** * Common alerts table row actions */ export const DefaultAlertActions = ( - props: Pick< - AlertActionsProps, - | 'alert' - | 'openAlertInFlyout' - | 'onActionExecuted' - | 'isAlertDetailsEnabled' - | 'resolveAlertPagePath' - | 'tableId' - | 'resolveRulePagePath' - | 'refresh' - > + props: AlertActionsProps ) => { - const { http, notifications } = useKibana<{ - http: HttpStart; - notifications: NotificationsStart; - }>().services; + const { + services: { + http, + notifications: { toasts }, + }, + } = useAlertsTableContext(); const { authorizedToCreateAnyRules } = useLoadRuleTypesQuery({ filteredRuleTypes: [], http, - toasts: notifications.toasts, + toasts, context: AlertsQueryContext, }); - const { - alert, - resolveRulePagePath, - refresh, - onActionExecuted, - tableId, - openAlertInFlyout, - isAlertDetailsEnabled, - resolveAlertPagePath, - } = props; - return ( <> - - - {authorizedToCreateAnyRules && ( - - )} - {authorizedToCreateAnyRules && ( - - )} + + + {authorizedToCreateAnyRules && } + {authorizedToCreateAnyRules && } ); }; diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx index 5e5864a5ee4c9..66f08e0b4d5ee 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/mark_as_untracked_alert_action.tsx @@ -11,12 +11,10 @@ import React, { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiContextMenuItem } from '@elastic/eui'; import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { HttpStart } from '@kbn/core-http-browser'; -import type { NotificationsStart } from '@kbn/core-notifications-browser'; import type { AdditionalContext, AlertActionsProps } from '../types'; import { useBulkUntrackAlerts } from '../hooks/use_bulk_untrack_alerts'; import { typedMemo } from '../utils/react'; +import { useAlertsTableContext } from '../contexts/alerts_table_context'; /** * Alerts table row action to mark the selected alert as untracked @@ -26,11 +24,10 @@ export const MarkAsUntrackedAlertAction = typedMemo( alert, refresh, onActionExecuted, - }: Pick, 'alert' | 'refresh' | 'onActionExecuted'>) => { - const { http, notifications } = useKibana<{ - http: HttpStart; - notifications: NotificationsStart; - }>().services; + }: AlertActionsProps) => { + const { + services: { http, notifications }, + } = useAlertsTableContext(); const { mutateAsync: untrackAlerts } = useBulkUntrackAlerts({ http, notifications }); const isAlertActive = useMemo(() => alert[ALERT_STATUS]?.[0] === ALERT_STATUS_ACTIVE, [alert]); diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/mute_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/mute_alert_action.tsx index 7a0018b8b8313..8eedfaa2e653e 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/mute_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/mute_alert_action.tsx @@ -13,13 +13,11 @@ import { i18n } from '@kbn/i18n'; import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; import { useMuteAlertInstance } from '@kbn/response-ops-alerts-apis/hooks/use_mute_alert_instance'; import { useUnmuteAlertInstance } from '@kbn/response-ops-alerts-apis/hooks/use_unmute_alert_instance'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { HttpStart } from '@kbn/core-http-browser'; -import type { NotificationsStart } from '@kbn/core-notifications-browser'; -import { typedMemo } from '../utils/react'; -import { useAlertMutedState } from '../hooks/use_alert_muted_state'; -import { MUTE, UNMUTE } from '../translations'; import type { AdditionalContext, AlertActionsProps } from '../types'; +import { MUTE, UNMUTE } from '../translations'; +import { useAlertMutedState } from '../hooks/use_alert_muted_state'; +import { typedMemo } from '../utils/react'; +import { useAlertsTableContext } from '../contexts/alerts_table_context'; /** * Alerts table row action to mute/unmute the selected alert @@ -29,11 +27,10 @@ export const MuteAlertAction = typedMemo( alert, refresh, onActionExecuted, - }: Pick, 'alert' | 'refresh' | 'onActionExecuted'>) => { - const { http, notifications } = useKibana<{ - http: HttpStart; - notifications: NotificationsStart; - }>().services; + }: AlertActionsProps) => { + const { + services: { http, notifications }, + } = useAlertsTableContext(); const { isMuted, ruleId, rule, alertInstanceId } = useAlertMutedState(alert); const { mutateAsync: muteAlert } = useMuteAlertInstance({ http, notifications }); const { mutateAsync: unmuteAlert } = useUnmuteAlertInstance({ http, notifications }); diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx index b89c56be9e106..7323615c505fa 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/view_alert_details_alert_action.tsx @@ -11,10 +11,9 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiContextMenuItem } from '@elastic/eui'; import { ALERT_UUID } from '@kbn/rule-data-utils'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { HttpStart } from '@kbn/core-http-browser'; -import { typedMemo } from '../utils/react'; +import { useAlertsTableContext } from '../contexts/alerts_table_context'; import type { AdditionalContext, AlertActionsProps } from '../types'; +import { typedMemo } from '../utils/react'; /** * Alerts table row action to open the selected alert detail page @@ -27,21 +26,17 @@ export const ViewAlertDetailsAlertAction = typedMemo( isAlertDetailsEnabled, resolveAlertPagePath, tableId, - }: Pick< - AlertActionsProps, - | 'alert' - | 'openAlertInFlyout' - | 'onActionExecuted' - | 'isAlertDetailsEnabled' - | 'resolveAlertPagePath' - | 'tableId' - >) => { - const { http } = useKibana<{ - http: HttpStart; - }>().services; + }: AlertActionsProps) => { + const { + services: { + http: { + basePath: { prepend }, + }, + }, + } = useAlertsTableContext(); const alertId = (alert[ALERT_UUID]?.[0] as string) ?? null; const pagePath = alertId && tableId && resolveAlertPagePath?.(alertId, tableId); - const linkToAlert = pagePath ? http.basePath.prepend(pagePath) : null; + const linkToAlert = pagePath ? prepend(pagePath) : null; if (isAlertDetailsEnabled && linkToAlert) { return ( diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx index 6d29d10dddfc1..d1221aa40d34d 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/view_rule_details_alert_action.tsx @@ -11,8 +11,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiContextMenuItem } from '@elastic/eui'; import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { HttpStart } from '@kbn/core-http-browser'; +import { useAlertsTableContext } from '../contexts/alerts_table_context'; import type { AdditionalContext, AlertActionsProps } from '../types'; import { typedMemo } from '../utils/react'; @@ -24,13 +23,17 @@ export const ViewRuleDetailsAlertAction = typedMemo( alert, resolveRulePagePath, tableId, - }: Pick, 'alert' | 'resolveRulePagePath' | 'tableId'>) => { - const { http } = useKibana<{ - http: HttpStart; - }>().services; + }: AlertActionsProps) => { + const { + services: { + http: { + basePath: { prepend }, + }, + }, + } = useAlertsTableContext(); const ruleId = (alert[ALERT_RULE_UUID]?.[0] as string) ?? null; const pagePath = ruleId && tableId && resolveRulePagePath?.(ruleId, tableId); - const linkToRule = pagePath ? http.basePath.prepend(pagePath) : null; + const linkToRule = pagePath ? prepend(pagePath) : null; if (!linkToRule) { return null; From db8c604c87c33b8e7d26885a72ee4f86d5958776 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Mar 2025 18:08:41 +0100 Subject: [PATCH 09/36] revert --- .../solutions/observability/plugins/observability/kibana.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/observability/kibana.jsonc b/x-pack/solutions/observability/plugins/observability/kibana.jsonc index 5a33e6b6e411d..e83a03dadd23d 100644 --- a/x-pack/solutions/observability/plugins/observability/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability/kibana.jsonc @@ -45,7 +45,7 @@ "logsShared", "licensing", "navigation", - "fieldsMetadata", + "fieldsMetadata" ], "optionalPlugins": [ "discover", From d581f06492e22c9ee4c2c9e43a713c80f7de0173 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 25 Mar 2025 18:27:35 +0100 Subject: [PATCH 10/36] handle types --- .../alert_actions/alert_actions.tsx | 34 ++++++++++++------- .../alerts_table/common/cell_value.tsx | 30 +++++++++++----- .../alerts_table/common/get_columns.tsx | 2 +- .../public/components/alerts_table/types.ts | 2 -- .../related_alerts/get_related_columns.tsx | 9 +++-- .../related_alerts/related_alerts_cell.tsx | 28 --------------- .../related_alerts/related_alerts_table.tsx | 2 -- 7 files changed, 49 insertions(+), 58 deletions(-) delete mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_cell.tsx diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index 6051751816489..8482658627df6 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -21,29 +21,27 @@ import { useRouteMatch } from 'react-router-dom'; import { SLO_ALERTS_TABLE_ID } from '@kbn/observability-shared-plugin/common'; import { DefaultAlertActions } from '@kbn/response-ops-alerts-table/components/default_alert_actions'; import { ALERT_UUID } from '@kbn/rule-data-utils'; -import type { AlertActionsProps } from '@kbn/response-ops-alerts-table/types'; import { useKibana } from '../../utils/kibana_react'; import { useCaseActions } from './use_case_actions'; import { RULE_DETAILS_PAGE_ID } from '../../pages/rule_details/constants'; import { paths, SLO_DETAIL_PATH } from '../../../common/locators/paths'; import { parseAlert } from '../../pages/alerts/helpers/parse_alert'; -import { ObservabilityAlertsTableContext, observabilityFeatureId } from '../..'; +import { + GetObservabilityAlertsTableProp, + ObservabilityAlertsTableContext, + observabilityFeatureId, +} from '../..'; import { ALERT_DETAILS_PAGE_ID } from '../../pages/alert_details/alert_details'; -export type ObsAlertActionProps = Pick< - AlertActionsProps, - 'alert' | 'openAlertInFlyout' | 'tableId' | 'refresh' -> & - ObservabilityAlertsTableContext; - -export function AlertActions({ +export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> = ({ observabilityRuleTypeRegistry, alert, tableId, refresh, openAlertInFlyout, parentAlert, -}: ObsAlertActionProps) { + ...rest +}) => { const services = useKibana().services; const { @@ -128,7 +126,8 @@ export function AlertActions({ : []), useMemo( () => ( - + observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} key="defaultRowActions" onActionExecuted={closeActionsPopover} isAlertDetailsEnabled={true} @@ -144,9 +143,18 @@ export function AlertActions({ refresh={refresh} alert={alert} openAlertInFlyout={openAlertInFlyout} + {...rest} /> ), - [alert, closeActionsPopover, openAlertInFlyout, refresh, tableId] + [ + alert, + closeActionsPopover, + observabilityRuleTypeRegistry, + openAlertInFlyout, + refresh, + rest, + tableId, + ] ), ]; @@ -236,7 +244,7 @@ export function AlertActions({ ); -} +}; // Default export used for lazy loading // eslint-disable-next-line import/no-default-export diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx index 9c17f2ff9f568..996367f1d35b6 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiLink } from '@elastic/eui'; +import { EuiLink, EuiText } from '@elastic/eui'; import React, { ReactNode } from 'react'; import { ALERT_DURATION, @@ -25,7 +25,12 @@ import { } from '@kbn/rule-data-utils'; import { isEmpty } from 'lodash'; import type { Alert } from '@kbn/alerting-types'; -import { ObsAlertActionProps } from '../../alert_actions/alert_actions'; +import { + RELATED_ACTIONS_COL, + RELATED_ALERT_REASON, + RELATION_COL, +} from '../../../pages/alert_details/components/related_alerts/get_related_columns'; +import { RelationCol } from '../../../pages/alert_details/components/related_alerts/relation_col'; import { paths } from '../../../../common/locators/paths'; import { asDuration } from '../../../../common/utils/formatters'; import { AlertSeverityBadge } from '../../alert_severity_badge'; @@ -34,6 +39,7 @@ import { parseAlert } from '../../../pages/alerts/helpers/parse_alert'; import { CellTooltip } from './cell_tooltip'; import { TimestampTooltip } from './timestamp_tooltip'; import { GetObservabilityAlertsTableProp } from '../types'; +import AlertActions from '../../alert_actions/alert_actions'; export const getAlertFieldValue = (alert: Alert, fieldName: string) => { // can be updated when working on https://github.com/elastic/kibana/issues/140819 @@ -54,10 +60,7 @@ export const getAlertFieldValue = (alert: Alert, fieldName: string) => { return '--'; }; -export type AlertCellRenderers = Record< - string, - (value: string, props: ObsAlertActionProps) => ReactNode ->; +export type AlertCellRenderers = Record ReactNode>; /** * This implementation of `EuiDataGrid`'s `renderCellValue` @@ -72,7 +75,7 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa openAlertInFlyout, observabilityRuleTypeRegistry, services: { http }, - extraCellRenderers, + parentAlert, } = props; const cellRenderers: AlertCellRenderers = { @@ -127,10 +130,19 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa /> ); }, - ...(extraCellRenderers ?? {}), + [RELATION_COL]: (value) => { + return ; + }, + [RELATED_ALERT_REASON]: (value) => { + const val = getAlertFieldValue(alert, ALERT_REASON); + return {val}; + }, + [RELATED_ACTIONS_COL]: (val) => { + return ; + }, }; const val = getAlertFieldValue(alert, columnId); - return cellRenderers[columnId] ? cellRenderers[columnId](val, props) : <>{val}; + return cellRenderers[columnId] ? cellRenderers[columnId](val) : <>{val}; }; diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/get_columns.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/get_columns.tsx index fa2f10c3516e7..f99fd7e8fc3a3 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/get_columns.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/get_columns.tsx @@ -10,12 +10,12 @@ import { ALERT_EVALUATION_VALUE, ALERT_EVALUATION_THRESHOLD, ALERT_DURATION, - ALERT_REASON, ALERT_RULE_NAME, ALERT_START, ALERT_STATUS, ALERT_INSTANCE_ID, TAGS, + ALERT_REASON, } from '@kbn/rule-data-utils'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/types.ts b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/types.ts index dd03ca4919bfd..72693a28fe196 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/types.ts +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/types.ts @@ -8,12 +8,10 @@ import { SetOptional } from 'type-fest'; import type { AlertsTablePropsWithRef } from '@kbn/response-ops-alerts-table/types'; import type { ConfigSchema, ObservabilityRuleTypeRegistry, TopAlert } from '../..'; -import { AlertCellRenderers } from './common/cell_value'; export interface ObservabilityAlertsTableContext { observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; config: ConfigSchema; - extraCellRenderers?: AlertCellRenderers; parentAlert?: TopAlert; } diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx index fef3d3f08aef3..771e53a8a3738 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx @@ -6,9 +6,12 @@ */ import type { EuiDataGridColumn } from '@elastic/eui'; -import { ALERT_RULE_NAME, ALERT_STATUS, ALERT_REASON } from '@kbn/rule-data-utils'; +import { ALERT_RULE_NAME, ALERT_STATUS } from '@kbn/rule-data-utils'; import { i18n } from '@kbn/i18n'; -import { RELATED_ACTIONS_COL, RELATION_COL } from './related_alerts_cell'; + +export const RELATED_ALERT_REASON = 'relatedAlertReason'; +export const RELATION_COL = 'relatedRelation'; +export const RELATED_ACTIONS_COL = 'relatedActions'; export const getRelatedColumns = (): EuiDataGridColumn[] => { return [ @@ -30,7 +33,7 @@ export const getRelatedColumns = (): EuiDataGridColumn[] => { displayAsText: i18n.translate('xpack.observability.alertsTGrid.reasonDescription', { defaultMessage: 'Reason', }), - id: ALERT_REASON, + id: RELATED_ALERT_REASON, initialWidth: 300, }, { diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_cell.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_cell.tsx deleted file mode 100644 index f8251ff27e3ed..0000000000000 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_cell.tsx +++ /dev/null @@ -1,28 +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 from 'react'; -import { ALERT_REASON } from '@kbn/rule-data-utils'; -import { EuiText } from '@elastic/eui'; -import AlertActions from '../../../../components/alert_actions/alert_actions'; -import { RelationCol } from './relation_col'; -import { AlertCellRenderers } from '../../../../components/alerts_table/common/cell_value'; - -export const RELATION_COL = 'relation'; -export const RELATED_ACTIONS_COL = 'relatedActions'; - -export const relatedAlertsRowRenderer: AlertCellRenderers = { - [RELATION_COL]: (value, props) => { - return ; - }, - [ALERT_REASON]: (value) => { - return {value}; - }, - [RELATED_ACTIONS_COL]: (val, props) => { - return ; - }, -}; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx index 44bf9332180df..f29e5346a1a73 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx @@ -10,7 +10,6 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { AlertsTable } from '@kbn/response-ops-alerts-table'; import { SortOrder } from '@elastic/elasticsearch/lib/api/types'; -import { relatedAlertsRowRenderer } from './related_alerts_cell'; import { getRelatedColumns } from './get_related_columns'; import { useBuildRelatedAlertsQuery } from '../../hooks/related_alerts/use_build_related_alerts_query'; import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; @@ -82,7 +81,6 @@ export function RelatedAlertsTable({ alertData }: Props) { additionalContext={{ observabilityRuleTypeRegistry, config, - extraCellRenderers: relatedAlertsRowRenderer, parentAlert: alert, }} renderCellValue={AlertsTableCellValue} From f27c5d2f537528b72b3d997aeff8d0a0eba425b0 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:41:49 +0000 Subject: [PATCH 11/36] [CI] Auto-commit changed files from 'node scripts/styled_components_mapping' --- .../solutions/observability/plugins/observability/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/observability/tsconfig.json b/x-pack/solutions/observability/plugins/observability/tsconfig.json index eb5ed92419d1e..8836267ca9546 100644 --- a/x-pack/solutions/observability/plugins/observability/tsconfig.json +++ b/x-pack/solutions/observability/plugins/observability/tsconfig.json @@ -79,7 +79,6 @@ "@kbn/alerting-rule-utils", "@kbn/ui-theme", "@kbn/core-application-common", - "@kbn/securitysolution-ecs", "@kbn/alerts-as-data-utils", "@kbn/datemath", "@kbn/logs-shared-plugin", From b478b596fb6b18af77c85e4237d8174551e11acb Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 26 Mar 2025 12:34:00 +0100 Subject: [PATCH 12/36] remove unused --- .../plugins/private/translations/translations/fr-FR.json | 2 -- .../plugins/private/translations/translations/ja-JP.json | 2 -- .../plugins/private/translations/translations/zh-CN.json | 2 -- 3 files changed, 6 deletions(-) diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 6b0a0043bf2ae..f31f6824a26cc 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -32634,8 +32634,6 @@ "xpack.observability.pages.alertDetails.pageTitle.ruleName": "Règle", "xpack.observability.pages.alertDetails.pageTitle.title": "{ruleCategory} {ruleCategory, select, Anomaly {détectée} Inventory {seuil dépassé} other {dépassés}}", "xpack.observability.pages.alertDetails.pageTitle.triggered": "Déclenché", - "xpack.observability.pages.alertDetails.relatedAlerts.empty.description": "En raison d'une erreur inattendue, aucune alerte associée ne peut être trouvée.", - "xpack.observability.pages.alertDetails.relatedAlerts.empty.title": "Problème de chargement des alertes associées", "xpack.observability.profilingAWSCostDiscountRateUiSettingDescription": "Si vous êtes inscrits au programme de réduction AWS Enterprise Discount Program (EDP), entrez votre taux de réduction pour mettre à jour le calcul des coûts de profilage.", "xpack.observability.profilingAWSCostDiscountRateUiSettingName": "Taux de réduction AWS EDP (%)", "xpack.observability.profilingAzureCostDiscountRateUiSettingDescription": "Si vous avez un accord Azure Enterprise avec Microsoft, saisissez votre taux de réduction pour mettre à jour le calcul du coût de profilage.", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 954f0dc72a95e..7fea912199669 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -32612,8 +32612,6 @@ "xpack.observability.pages.alertDetails.pageTitle.ruleName": "ルール", "xpack.observability.pages.alertDetails.pageTitle.title": "{ruleCategory} {ruleCategory, select, Anomaly {検出されました} Inventory {しきい値に違反しました} other {違反しました}}", "xpack.observability.pages.alertDetails.pageTitle.triggered": "実行済み", - "xpack.observability.pages.alertDetails.relatedAlerts.empty.description": "予期しないエラーのため、関連するアラートが見つかりません。", - "xpack.observability.pages.alertDetails.relatedAlerts.empty.title": "関連するアラートの読み込みエラー", "xpack.observability.profilingAWSCostDiscountRateUiSettingDescription": "AWS Enterprise Discount Program(EDP)に加入している場合は、割引率を入力してプロファイリング費用の計算を更新します。", "xpack.observability.profilingAWSCostDiscountRateUiSettingName": "AWS EDP割引率(%)", "xpack.observability.profilingAzureCostDiscountRateUiSettingDescription": "MicrosoftとのAzureエンタープライズ契約がある場合は、割引率を入力して、プロファイリングコスト計算を更新してください。", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 1c0707307328d..6bfd624aeee82 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -32668,8 +32668,6 @@ "xpack.observability.pages.alertDetails.pageTitle.ruleName": "规则", "xpack.observability.pages.alertDetails.pageTitle.title": "{ruleCategory} {ruleCategory, select, Anomaly {已检测到} Inventory {超出阈值} other {已超出}}", "xpack.observability.pages.alertDetails.pageTitle.triggered": "已触发", - "xpack.observability.pages.alertDetails.relatedAlerts.empty.description": "由于出现意外错误,找不到相关告警。", - "xpack.observability.pages.alertDetails.relatedAlerts.empty.title": "加载相关告警时出现问题", "xpack.observability.profilingAWSCostDiscountRateUiSettingDescription": "如果已加入 AWS 企业折扣计划 (EDP),请输入您的折扣率以更新分析成本计算。", "xpack.observability.profilingAWSCostDiscountRateUiSettingName": "AWS EDP 折扣率 (%)", "xpack.observability.profilingAzureCostDiscountRateUiSettingDescription": "如果与 Microsoft 签署了 Azure 企业协议,请输入您的折扣率以更新分析成本计算。", From ba24dd787ac57f386bd8bd4ad545848b91e8a4d4 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 26 Mar 2025 18:01:43 +0100 Subject: [PATCH 13/36] fix jest tests --- .../alert_actions/alert_actions.test.tsx | 31 +++++++++++++------ .../alerts_table/common/cell_value.test.tsx | 2 ++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.test.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.test.tsx index 7e6a317357a67..ea9d84a89d191 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.test.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.test.tsx @@ -29,7 +29,7 @@ import { ObservabilityRuleTypeRegistry } from '../../rules/create_observability_ import type { GetObservabilityAlertsTableProp } from '../..'; import { AlertsTableContextProvider } from '@kbn/response-ops-alerts-table/contexts/alerts_table_context'; import { AdditionalContext, RenderContext } from '@kbn/response-ops-alerts-table/types'; - +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; const refresh = jest.fn(); const caseHooksReturnedValue = { open: () => { @@ -82,6 +82,15 @@ jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ ObservabilityPageTemplate: KibanaPageTemplate, ObservabilityAIAssistantContextualInsight, })); +jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ + appMountParameters: {} as AppMountParameters, + core: {} as CoreStart, + config, + plugins: {} as ObservabilityPublicPluginsStart, + observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), + ObservabilityPageTemplate: KibanaPageTemplate, + ObservabilityAIAssistantContextualInsight, +})); describe('ObservabilityActions component', () => { beforeEach(() => { @@ -145,15 +154,17 @@ describe('ObservabilityActions component', () => { const wrapper = mountWithIntl( - - - - >)} - /> - - + + + + + >)} + /> + + + ); await act(async () => { diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.test.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.test.tsx index a16796bdb0eef..9eca4fb56988c 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.test.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.test.tsx @@ -10,6 +10,7 @@ import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/ import { render } from '../../../utils/test_helper'; import { AlertsTableCellValue } from './cell_value'; import { Alert } from '@kbn/alerting-types'; +import { coreMock } from '@kbn/core/public/mocks'; interface AlertsTableRow { alertStatus: typeof ALERT_STATUS_ACTIVE | typeof ALERT_STATUS_RECOVERED; @@ -66,4 +67,5 @@ const requiredProperties = { isDraggable: false, linkValues: [], scopeId: '', + services: coreMock.createStart(), } as unknown as ComponentProps; From bcd33836b4f97a31d931170d4ebd737d8c1a4ed7 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 26 Mar 2025 19:24:31 +0100 Subject: [PATCH 14/36] fix more jest tests --- .../common/apis/search_alerts/search_alerts.test.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.test.tsx b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.test.tsx index 98a1e4cf978f7..7237889ecb951 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.test.tsx +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.test.tsx @@ -85,6 +85,7 @@ const parsedAlerts = { { _index: '.internal.alerts-security.alerts-default-000001', _id: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', + _score: 1, '@timestamp': ['2022-03-22T16:48:07.518Z'], 'host.name': ['Host-4dbzugdlqd'], 'kibana.alert.reason': [ @@ -99,6 +100,7 @@ const parsedAlerts = { { _index: '.internal.alerts-security.alerts-default-000001', _id: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', + _score: 1, '@timestamp': ['2022-03-22T16:17:50.769Z'], 'host.name': ['Host-4dbzugdlqd'], 'kibana.alert.reason': [ @@ -130,6 +132,7 @@ const parsedAlerts = { host: { name: ['Host-4dbzugdlqd'] }, _id: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', _index: '.internal.alerts-security.alerts-default-000001', + _score: 1, }, { kibana: { @@ -148,6 +151,7 @@ const parsedAlerts = { host: { name: ['Host-4dbzugdlqd'] }, _id: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', _index: '.internal.alerts-security.alerts-default-000001', + _score: 1, }, ], oldAlertsData: [ @@ -169,6 +173,10 @@ const parsedAlerts = { field: '_id', value: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', }, + { + field: '_score', + value: 1, + }, { field: '_index', value: '.internal.alerts-security.alerts-default-000001' }, ], [ @@ -189,6 +197,10 @@ const parsedAlerts = { field: '_id', value: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', }, + { + field: '_score', + value: 1, + }, { field: '_index', value: '.internal.alerts-security.alerts-default-000001' }, ], ], From 0890214e190d63c2354b1d593d802dca19a89cd9 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 26 Mar 2025 19:27:14 +0100 Subject: [PATCH 15/36] fix more jest tests --- .../common/hooks/use_search_alerts_query.test.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx index c3ccf7528b9e1..e3253abd2646c 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx @@ -166,6 +166,7 @@ describe('useSearchAlertsQuery', () => { { _index: '.internal.alerts-security.alerts-default-000001', _id: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', + _score: 1, '@timestamp': ['2022-03-22T16:48:07.518Z'], 'host.name': ['Host-4dbzugdlqd'], 'kibana.alert.reason': [ @@ -180,6 +181,7 @@ describe('useSearchAlertsQuery', () => { { _index: '.internal.alerts-security.alerts-default-000001', _id: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', + _score: 1, '@timestamp': ['2022-03-22T16:17:50.769Z'], 'host.name': ['Host-4dbzugdlqd'], 'kibana.alert.reason': [ @@ -211,6 +213,7 @@ describe('useSearchAlertsQuery', () => { host: { name: ['Host-4dbzugdlqd'] }, _id: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', _index: '.internal.alerts-security.alerts-default-000001', + _score: 1, }, { kibana: { @@ -229,6 +232,7 @@ describe('useSearchAlertsQuery', () => { host: { name: ['Host-4dbzugdlqd'] }, _id: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', _index: '.internal.alerts-security.alerts-default-000001', + _score: 1, }, ], oldAlertsData: [ @@ -250,6 +254,10 @@ describe('useSearchAlertsQuery', () => { field: '_id', value: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', }, + { + field: '_score', + value: 1, + }, { field: '_index', value: '.internal.alerts-security.alerts-default-000001' }, ], [ @@ -270,6 +278,10 @@ describe('useSearchAlertsQuery', () => { field: '_id', value: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', }, + { + field: '_score', + value: 1, + }, { field: '_index', value: '.internal.alerts-security.alerts-default-000001' }, ], ], From b20da11c938388934c6467522933f133a7a0dee1 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 27 Mar 2025 11:37:48 +0100 Subject: [PATCH 16/36] fix tests --- .../public/components/alerts_table/common/cell_value.tsx | 2 +- .../journeys/alert_rules/default_status_alert.journey.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx index 996367f1d35b6..af514806fadf1 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx @@ -122,7 +122,7 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa return ( + {value} } diff --git a/x-pack/solutions/observability/plugins/synthetics/e2e/synthetics/journeys/alert_rules/default_status_alert.journey.ts b/x-pack/solutions/observability/plugins/synthetics/e2e/synthetics/journeys/alert_rules/default_status_alert.journey.ts index 921d99c2b642f..d0b84f69b29cc 100644 --- a/x-pack/solutions/observability/plugins/synthetics/e2e/synthetics/journeys/alert_rules/default_status_alert.journey.ts +++ b/x-pack/solutions/observability/plugins/synthetics/e2e/synthetics/journeys/alert_rules/default_status_alert.journey.ts @@ -120,11 +120,12 @@ journey(`DefaultStatusAlert`, async ({ page, params }) => { await retry.tryForTime(3 * 60 * 1000, async () => { await page.click(byTestId('querySubmitButton')); + await page.waitForTimeout(5000); const alerts = await page.waitForSelector(`text=1 Alert`, { timeout: 5 * 1000 }); expect(await alerts.isVisible()).toBe(true); - const text = await page.textContent(`${byTestId('dataGridRowCell')} .euiLink`); + const text = await page.getByTestId('o11yGetRenderCellValueLink').textContent(); expect(text).toBe(reasonMessage); }); From 469095058c9b7cd38bd37585cfbb85899c6788c9 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 27 Mar 2025 12:57:29 +0100 Subject: [PATCH 17/36] fix tests --- .../public/components/alerts_table/alerts_table.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/alerts_table.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/alerts_table.tsx index dd8824f2a0100..0c15c6f31ae36 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/alerts_table.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/alerts_table.tsx @@ -20,6 +20,7 @@ import { } from './types'; import { AlertsTableCellValue } from './common/cell_value'; import { AlertsFlyoutBody } from '../alerts_flyout/alerts_flyout_body'; +import { AlertsFlyoutHeader } from '../alerts_flyout/alerts_flyout_header'; import { AlertsFlyoutFooter } from '../alerts_flyout/alerts_flyout_footer'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { getColumns } from './common/get_columns'; @@ -67,6 +68,7 @@ export function ObservabilityAlertsTable(props: Omit Date: Fri, 28 Mar 2025 06:27:54 +0100 Subject: [PATCH 18/36] PR feedback --- .../search_strategy_types.ts | 1 + .../apis/search_alerts/search_alerts.ts | 6 ++++++ .../common/hooks/use_search_alerts_query.ts | 2 ++ .../alerts-table/components/alerts_table.tsx | 5 ++++- .../shared/response-ops/alerts-table/types.ts | 1 + .../server/search_strategy/search_strategy.ts | 1 + .../alert_actions/alert_actions.tsx | 1 - .../alerts_table/common/cell_value.tsx | 4 ++++ .../related_alerts/get_related_columns.tsx | 11 ++++++++-- .../related_alerts/related_alerts_table.tsx | 20 +++---------------- .../use_build_related_alerts_query.ts | 2 +- 11 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/platform/packages/shared/kbn-alerting-types/search_strategy_types.ts b/src/platform/packages/shared/kbn-alerting-types/search_strategy_types.ts index 3568882d6f891..8231d1076953c 100644 --- a/src/platform/packages/shared/kbn-alerting-types/search_strategy_types.ts +++ b/src/platform/packages/shared/kbn-alerting-types/search_strategy_types.ts @@ -24,6 +24,7 @@ export type RuleRegistrySearchRequest = IEsSearchRequest & { sort?: SortCombinations[]; pagination?: RuleRegistrySearchRequestPagination; runtimeMappings?: MappingRuntimeFields; + minScore?: number; }; export interface RuleRegistrySearchRequestPagination { diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts index 55c8e2fb3e351..0d6ec642fc59f 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts @@ -68,6 +68,10 @@ export interface SearchAlertsParams { * The page size to fetch */ pageSize: number; + /** + * The minimum score to apply to the query + */ + minScore?: number; } export interface SearchAlertsResult { @@ -92,6 +96,7 @@ export const searchAlerts = ({ runtimeMappings, pageIndex, pageSize, + minScore, }: SearchAlertsParams): Promise => lastValueFrom( data.search @@ -104,6 +109,7 @@ export const searchAlerts = ({ pagination: { pageIndex, pageSize }, sort, runtimeMappings, + minScore, }, { strategy: 'privateRuleRegistryAlertsSearchStrategy', diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts index 4450afa788c39..c8e29e527db45 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts @@ -43,6 +43,7 @@ export const useSearchAlertsQuery = ({ data, ...params }: UseSearchAlertsQueryPa runtimeMappings, pageIndex = 0, pageSize = DEFAULT_ALERTS_PAGE_SIZE, + minScore, } = params; return useQuery({ queryKey: queryKeyPrefix.concat(JSON.stringify(params)), @@ -58,6 +59,7 @@ export const useSearchAlertsQuery = ({ data, ...params }: UseSearchAlertsQueryPa runtimeMappings, pageIndex, pageSize, + minScore, }), refetchOnWindowFocus: false, context: AlertsQueryContext, diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_table.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_table.tsx index 1f5c413d0117b..d46128aea50d9 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_table.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_table.tsx @@ -170,6 +170,7 @@ const AlertsTableContent = typedForwardRef( ruleTypeIds, consumers, query, + minScore, initialSort = DEFAULT_SORT, initialPageSize = DEFAULT_ALERTS_PAGE_SIZE, leadingControlColumns = DEFAULT_LEADING_CONTROL_COLUMNS, @@ -277,6 +278,7 @@ const AlertsTableContent = typedForwardRef( runtimeMappings, pageIndex: 0, pageSize: initialPageSize, + minScore, }); useEffect(() => { @@ -287,6 +289,7 @@ const AlertsTableContent = typedForwardRef( query, sort, runtimeMappings, + minScore, // Go back to the first page if the query changes pageIndex: !deepEqual(prevQueryParams, { ruleTypeIds, @@ -300,7 +303,7 @@ const AlertsTableContent = typedForwardRef( : oldPageIndex, pageSize: oldPageSize, })); - }, [ruleTypeIds, fields, query, runtimeMappings, sort, consumers]); + }, [ruleTypeIds, fields, query, runtimeMappings, sort, consumers, minScore]); const { data: alertsData, diff --git a/src/platform/packages/shared/response-ops/alerts-table/types.ts b/src/platform/packages/shared/response-ops/alerts-table/types.ts index 4833d03394246..635fba330c5d2 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/types.ts +++ b/src/platform/packages/shared/response-ops/alerts-table/types.ts @@ -372,6 +372,7 @@ export interface PublicAlertsDataGridProps | 'columns' > { ruleTypeIds: string[]; + minScore?: number; consumers?: string[]; /** * If true, shows a button in the table toolbar to inspect the search alerts request diff --git a/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.ts b/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.ts index d5386e5513f04..31a3966b392ac 100644 --- a/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.ts +++ b/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.ts @@ -175,6 +175,7 @@ export const ruleRegistrySearchStrategyProvider = ( from: request.pagination ? request.pagination.pageIndex * size : 0, query, ...(request.runtimeMappings ? { runtime_mappings: request.runtimeMappings } : {}), + ...(request.minScore ? { min_score: request.minScore } : {}), }, }; return (isAnyRuleTypeESAuthorized ? requestUserEs : internalUserEs).search( diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index 8482658627df6..bdd67f53c31ca 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -43,7 +43,6 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> ...rest }) => { const services = useKibana().services; - const { http: { basePath: { prepend }, diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx index af514806fadf1..9ac39f42aeb9a 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx @@ -22,6 +22,7 @@ import { ALERT_START, ALERT_RULE_EXECUTION_TIMESTAMP, ALERT_RULE_UUID, + ALERT_CASE_IDS, } from '@kbn/rule-data-utils'; import { isEmpty } from 'lodash'; import type { Alert } from '@kbn/alerting-types'; @@ -140,6 +141,9 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa [RELATED_ACTIONS_COL]: (val) => { return ; }, + [ALERT_CASE_IDS]: (value) => { + return <>{value}; + }, }; const val = getAlertFieldValue(alert, columnId); diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx index 771e53a8a3738..a072c995ef0fc 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx @@ -6,7 +6,7 @@ */ import type { EuiDataGridColumn } from '@elastic/eui'; -import { ALERT_RULE_NAME, ALERT_STATUS } from '@kbn/rule-data-utils'; +import { ALERT_CASE_IDS, ALERT_RULE_NAME, ALERT_STATUS } from '@kbn/rule-data-utils'; import { i18n } from '@kbn/i18n'; export const RELATED_ALERT_REASON = 'relatedAlertReason'; @@ -41,13 +41,20 @@ export const getRelatedColumns = (): EuiDataGridColumn[] => { defaultMessage: 'Relation', }), id: RELATION_COL, + initialWidth: 350, + }, + { + id: ALERT_CASE_IDS, + displayAsText: i18n.translate('xpack.observability.alertsTGrid.caseIdsColumnDescription', { + defaultMessage: 'Related cases', + }), }, { displayAsText: i18n.translate('xpack.observability.alertsTGrid.actionsColumnDescription', { defaultMessage: 'Actions', }), id: RELATED_ACTIONS_COL, - initialWidth: 150, + initialWidth: 120, }, ]; }; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx index f29e5346a1a73..fc8d48a699836 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { EuiCallOut, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { AlertsTable } from '@kbn/response-ops-alerts-table'; import { SortOrder } from '@elastic/elasticsearch/lib/api/types'; @@ -56,26 +55,12 @@ export function RelatedAlertsTable({ alertData }: Props) { return ( - -

- {i18n.translate('xpack.observability.relatedAlertsView.p.weAreFetchingAlertsLabel', { - defaultMessage: - "We are fetching relevant alerts to the current alert based on some heuristics. Soon you'll be able to tweaks the weights applied to these heuristics", - })} -

-
id={RELATED_ALERTS_TABLE_ID} query={esQuery} columns={columns} ruleTypeIds={OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES} + minScore={1.5} initialSort={initialSort} casesConfiguration={caseConfiguration} additionalContext={{ @@ -97,6 +82,7 @@ export function RelatedAlertsTable({ alertData }: Props) { rowHeightsOptions={{ defaultHeight: 'auto', }} + height="600px" />
); diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts index b360a151d50c0..4bcff431a8d5d 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts @@ -176,7 +176,7 @@ export function useBuildRelatedAlertsQuery({ alert }: Props): QueryDslQueryConta weight: 5, }, ], - boost_mode: 'multiply', + boost_mode: 'sum', }, }, ], From 4f36da8ae9eb371718fde17a611d1a824304efa6 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Tue, 1 Apr 2025 16:01:12 -0400 Subject: [PATCH 19/36] add unit test --- .../search_strategy/search_strategy.test.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.test.ts b/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.test.ts index e34ece2b256d3..5ed835e18d851 100644 --- a/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.test.ts +++ b/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.test.ts @@ -716,4 +716,62 @@ describe('ruleRegistrySearchStrategyProvider()', () => { } `); }); + + it('passes the min_score if minScore is provided', async () => { + const minScore = 0.5; + const request: RuleRegistrySearchRequest = { + ruleTypeIds: ['siem.esqlRule'], + minScore, + }; + const options = {}; + const deps = { + request: {}, + }; + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['security-siem']); + + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); + + await lastValueFrom( + strategy.search(request, options, deps as unknown as SearchStrategyDependencies) + ); + + const arg0 = searchStrategySearch.mock.calls[0][0]; + expect(arg0.params.body.fields.length).toEqual( + // +2 because of fields.push({ field: 'kibana.alert.*', include_unmapped: false }); and + // fields.push({ field: 'signal.*', include_unmapped: false }); + ALERT_EVENTS_FIELDS.length + 2 + ); + + expect.arrayContaining([ + expect.objectContaining({ + x: 2, + y: 3, + }), + ]); + + expect(arg0).toEqual( + expect.objectContaining({ + id: undefined, + params: expect.objectContaining({ + allow_no_indices: true, + body: expect.objectContaining({ + _source: false, + fields: expect.arrayContaining([ + expect.objectContaining({ + field: '@timestamp', + include_unmapped: true, + }), + ]), + from: 0, + min_score: minScore, + size: 1000, + sort: [], + }), + ignore_unavailable: true, + index: ['security-siem'], + }), + }) + ); + }); }); From a0c45c699d7190600d1940d048aa888d039effef Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Tue, 1 Apr 2025 16:41:32 -0400 Subject: [PATCH 20/36] add integration test --- .../tests/basic/search_strategy.ts | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts index 6462e2a1ec807..31cfe83824dc9 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { ALERT_START } from '@kbn/rule-data-utils'; import type { RuleRegistrySearchResponse } from '@kbn/rule-registry-plugin/common'; import type { FtrProviderContext } from '../../../common/ftr_provider_context'; import { @@ -692,6 +693,90 @@ export default ({ getService }: FtrProviderContext) => { }); }); + describe('observability', () => { + const apmRuleTypeIds = ['apm.transaction_error_rate', 'apm.error_rate']; + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); + }); + + it('should omit alerts when score is less than min score', async () => { + const query = { + bool: { + filter: [], + should: [ + { + function_score: { + functions: [ + { + exp: { + [ALERT_START]: { + origin: '2021-10-19T14:58:08.539Z', + scale: '10m', + offset: '10m', + decay: 0.5, + }, + }, + weight: 10, + }, + ], + boost_mode: 'sum', + }, + }, + ], + must: [], + must_not: [], + }, + }; + const resultWithoutMinScore = await secureSearch.send({ + supertestWithoutAuth, + auth: { + username: obsOnlySpacesAll.username, + password: obsOnlySpacesAll.password, + }, + referer: 'test', + kibanaVersion, + internalOrigin: 'Kibana', + options: { + ruleTypeIds: apmRuleTypeIds, + query, + }, + strategy: 'privateRuleRegistryAlertsSearchStrategy', + space: 'default', + }); + + expect(resultWithoutMinScore.rawResponse.hits.total).to.eql(9); + + validateRuleTypeIds(resultWithoutMinScore, apmRuleTypeIds); + + const resultWithMinScore = await secureSearch.send({ + supertestWithoutAuth, + auth: { + username: obsOnlySpacesAll.username, + password: obsOnlySpacesAll.password, + }, + referer: 'test', + kibanaVersion, + internalOrigin: 'Kibana', + options: { + ruleTypeIds: apmRuleTypeIds, + query, + minScore: 11, + }, + strategy: 'privateRuleRegistryAlertsSearchStrategy', + space: 'default', + }); + + expect(resultWithMinScore.rawResponse.hits.total).to.eql(8); + + validateRuleTypeIds(resultWithMinScore, apmRuleTypeIds); + }); + }); + describe('discover', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); From 1c3de19af758c1f945ffbcb9f2a6a137e697f685 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Wed, 2 Apr 2025 09:36:53 -0400 Subject: [PATCH 21/36] adjust types --- .../shared/kbn-alerting-types/alerts_types.ts | 4 +-- .../components/default_alerts_flyout.tsx | 3 ++- .../components/default_cell_value.tsx | 2 +- .../alerts_table/common/cell_value.tsx | 3 ++- .../related_alerts/relation_col.tsx | 26 +++++++++++++++---- 5 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/platform/packages/shared/kbn-alerting-types/alerts_types.ts b/src/platform/packages/shared/kbn-alerting-types/alerts_types.ts index e354a58c966e4..a4708817c8cd3 100644 --- a/src/platform/packages/shared/kbn-alerting-types/alerts_types.ts +++ b/src/platform/packages/shared/kbn-alerting-types/alerts_types.ts @@ -13,7 +13,7 @@ import type { JsonValue } from '@kbn/utility-types'; export interface MetaAlertFields { _id: string; _index: string; - _score?: string | JsonValue[]; + _score?: number; } export interface LegacyField { @@ -30,7 +30,7 @@ export type KnownAlertFields = { [Property in TechnicalRuleDataFieldName]?: JsonValue[]; }; -export type UnknownAlertFields = Record; +export type UnknownAlertFields = Record; /** * Alert document type as returned by alerts search requests diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/default_alerts_flyout.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/default_alerts_flyout.tsx index 77c3cc66733fc..a055869b82a53 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/default_alerts_flyout.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/default_alerts_flyout.tsx @@ -12,6 +12,7 @@ import { EuiDescriptionList, EuiPanel, EuiTabbedContentTab, EuiTitle } from '@el import { ALERT_RULE_NAME } from '@kbn/rule-data-utils'; import { i18n } from '@kbn/i18n'; import { ScrollableFlyoutTabbedContent, AlertFieldsTable } from '@kbn/alerts-ui-shared'; +import { JsonValue } from '@kbn/utility-types'; import { AdditionalContext, FlyoutSectionProps } from '../types'; import { defaultAlertsTableColumns } from '../configuration'; import { DefaultCellValue } from './default_cell_value'; @@ -43,7 +44,7 @@ export const DefaultAlertsFlyoutBody = ( { - const value = alert[column.id]?.[0]; + const value = (alert[column.id] as JsonValue[])?.[0]; return { title: (column.displayAsText as string) ?? column.id, diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/default_cell_value.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/default_cell_value.tsx index 97bcdc88a8adb..8cfeba5c63c62 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/default_cell_value.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/default_cell_value.tsx @@ -94,7 +94,7 @@ export const DefaultCellValue = ({ /** * Extracts the value from the raw json ES field */ -const extractFieldValue = (rawValue: string | JsonValue[]) => { +const extractFieldValue = (rawValue: string | number | JsonValue[]) => { const value = Array.isArray(rawValue) ? rawValue.join() : rawValue; if (!isEmpty(value)) { diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx index 9ac39f42aeb9a..724bde5cc118d 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx @@ -26,6 +26,7 @@ import { } from '@kbn/rule-data-utils'; import { isEmpty } from 'lodash'; import type { Alert } from '@kbn/alerting-types'; +import type { JsonValue } from '@kbn/utility-types'; import { RELATED_ACTIONS_COL, RELATED_ALERT_REASON, @@ -44,7 +45,7 @@ import AlertActions from '../../alert_actions/alert_actions'; export const getAlertFieldValue = (alert: Alert, fieldName: string) => { // can be updated when working on https://github.com/elastic/kibana/issues/140819 - const rawValue = alert[fieldName]; + const rawValue = alert[fieldName] as JsonValue[]; const value = Array.isArray(rawValue) ? rawValue.join() : rawValue; if (!isEmpty(value)) { diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx index f2ab0519e9da4..deb574c71099f 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx @@ -7,24 +7,36 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { ALERT_INSTANCE_ID, ALERT_RULE_TAGS, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { + ALERT_INSTANCE_ID, + ALERT_RULE_TAGS, + ALERT_RULE_UUID, + ALERT_RULE_NAME, +} from '@kbn/rule-data-utils'; import { intersection } from 'lodash'; -import { EuiDescriptionList } from '@elastic/eui'; +import { EuiDescriptionList, EuiLink } from '@elastic/eui'; import type { Alert } from '@kbn/alerting-types'; import { TagsList } from '@kbn/observability-shared-plugin/public'; import type { TopAlert } from '../../../..'; import { getAlertFieldValue } from '../../../../components/alerts_table/common/cell_value'; +import { paths } from '../../../../../common/locators/paths'; +import { useKibana } from '../../../../utils/kibana_react'; export function RelationCol({ alert, parentAlert }: { alert: Alert; parentAlert: TopAlert }) { + const { + services: { http }, + } = useKibana(); const instanceId = getAlertFieldValue(alert, ALERT_INSTANCE_ID); const tags = getAlertFieldValue(alert, ALERT_RULE_TAGS); - const ruleUuid = getAlertFieldValue(alert, ALERT_RULE_UUID); + const ruleId = getAlertFieldValue(alert, ALERT_RULE_UUID); + const ruleName = getAlertFieldValue(alert, ALERT_RULE_NAME); + const ruleLink = ruleId ? http.basePath.prepend(paths.observability.ruleDetails(ruleId)) : ''; const hasSomeRelationWithInstance = intersection(parentAlert.fields[ALERT_INSTANCE_ID].split(','), instanceId.split(',')).length > 0; const hasSomeRelationWithTags = intersection(parentAlert.fields[ALERT_RULE_TAGS], tags.split(',')).length > 0; - const hasRelationWithRule = ruleUuid === parentAlert.fields[ALERT_RULE_UUID]; + const hasRelationWithRule = ruleId === parentAlert.fields[ALERT_RULE_UUID]; const relations = []; if (hasSomeRelationWithInstance) { relations.push({ @@ -49,7 +61,11 @@ export function RelationCol({ alert, parentAlert }: { alert: Alert; parentAlert: title: i18n.translate('xpack.observability.columns.ruleBadgeLabel', { defaultMessage: 'Rule', }), - description: ruleUuid, + description: ( + + {ruleName} + + ), }); } return ( From 4ad695c31984b168e7974a66b0522796c4b52ac0 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Thu, 3 Apr 2025 10:55:49 -0400 Subject: [PATCH 22/36] remove sort fields control --- .../components/related_alerts/related_alerts_table.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx index fc8d48a699836..e8bd6521329bc 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx @@ -68,6 +68,9 @@ export function RelatedAlertsTable({ alertData }: Props) { config, parentAlert: alert, }} + toolbarVisibility={{ + showSortSelector: false, + }} renderCellValue={AlertsTableCellValue} renderFlyoutBody={AlertsFlyoutBody} renderFlyoutFooter={AlertsFlyoutFooter} From fa113578675d01ced07ca521de35680b57a9993f Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Thu, 3 Apr 2025 12:35:44 -0400 Subject: [PATCH 23/36] adjust sort order --- .../related_alerts/related_alerts_table.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx index e8bd6521329bc..ebe0cb12e83d8 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx @@ -7,6 +7,7 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import React from 'react'; +import { ALERT_START, ALERT_UUID } from '@kbn/rule-data-utils'; import { AlertsTable } from '@kbn/response-ops-alerts-table'; import { SortOrder } from '@elastic/elasticsearch/lib/api/types'; import { getRelatedColumns } from './get_related_columns'; @@ -30,11 +31,15 @@ interface Props { } const columns = getRelatedColumns(); -const initialSort = [ +const initialSort: Array> = [ { - ['_score']: { - order: 'desc' as SortOrder, - }, + _score: 'desc', + }, + { + [ALERT_START]: 'desc', + }, + { + [ALERT_UUID]: 'desc', }, ]; From c44723248696714c75b08fdbafad240398a0f363 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Mon, 7 Apr 2025 10:43:40 -0400 Subject: [PATCH 24/36] add ability to track scores when using field sorts --- .../shared/kbn-alerting-types/search_strategy_types.ts | 1 + .../src/common/apis/search_alerts/search_alerts.ts | 6 ++++++ .../src/common/hooks/use_search_alerts_query.ts | 2 ++ .../response-ops/alerts-table/components/alerts_table.tsx | 5 ++++- .../packages/shared/response-ops/alerts-table/types.ts | 1 + .../components/related_alerts/related_alerts_table.tsx | 1 + 6 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/platform/packages/shared/kbn-alerting-types/search_strategy_types.ts b/src/platform/packages/shared/kbn-alerting-types/search_strategy_types.ts index 8231d1076953c..1e50905890482 100644 --- a/src/platform/packages/shared/kbn-alerting-types/search_strategy_types.ts +++ b/src/platform/packages/shared/kbn-alerting-types/search_strategy_types.ts @@ -25,6 +25,7 @@ export type RuleRegistrySearchRequest = IEsSearchRequest & { pagination?: RuleRegistrySearchRequestPagination; runtimeMappings?: MappingRuntimeFields; minScore?: number; + trackScores?: boolean; }; export interface RuleRegistrySearchRequestPagination { diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts index 0d6ec642fc59f..21f33a768cba4 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/apis/search_alerts/search_alerts.ts @@ -72,6 +72,10 @@ export interface SearchAlertsParams { * The minimum score to apply to the query */ minScore?: number; + /** + * Whether to track the score of the query + */ + trackScores?: boolean; } export interface SearchAlertsResult { @@ -97,6 +101,7 @@ export const searchAlerts = ({ pageIndex, pageSize, minScore, + trackScores, }: SearchAlertsParams): Promise => lastValueFrom( data.search @@ -110,6 +115,7 @@ export const searchAlerts = ({ sort, runtimeMappings, minScore, + trackScores, }, { strategy: 'privateRuleRegistryAlertsSearchStrategy', diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts index a1c9aae9f116c..c942892808403 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.ts @@ -44,6 +44,7 @@ export const useSearchAlertsQuery = ({ data, ...params }: UseSearchAlertsQueryPa pageIndex = 0, pageSize = DEFAULT_ALERTS_PAGE_SIZE, minScore, + trackScores, } = params; return useQuery({ queryKey: queryKeyPrefix.concat(JSON.stringify(params)), @@ -60,6 +61,7 @@ export const useSearchAlertsQuery = ({ data, ...params }: UseSearchAlertsQueryPa pageIndex, pageSize, minScore, + trackScores, }), refetchOnWindowFocus: false, context: AlertsQueryContext, diff --git a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_table.tsx b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_table.tsx index d46128aea50d9..a110dca964d03 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/components/alerts_table.tsx +++ b/src/platform/packages/shared/response-ops/alerts-table/components/alerts_table.tsx @@ -171,6 +171,7 @@ const AlertsTableContent = typedForwardRef( consumers, query, minScore, + trackScores = false, initialSort = DEFAULT_SORT, initialPageSize = DEFAULT_ALERTS_PAGE_SIZE, leadingControlColumns = DEFAULT_LEADING_CONTROL_COLUMNS, @@ -279,6 +280,7 @@ const AlertsTableContent = typedForwardRef( pageIndex: 0, pageSize: initialPageSize, minScore, + trackScores, }); useEffect(() => { @@ -290,6 +292,7 @@ const AlertsTableContent = typedForwardRef( sort, runtimeMappings, minScore, + trackScores, // Go back to the first page if the query changes pageIndex: !deepEqual(prevQueryParams, { ruleTypeIds, @@ -303,7 +306,7 @@ const AlertsTableContent = typedForwardRef( : oldPageIndex, pageSize: oldPageSize, })); - }, [ruleTypeIds, fields, query, runtimeMappings, sort, consumers, minScore]); + }, [ruleTypeIds, fields, query, runtimeMappings, sort, consumers, minScore, trackScores]); const { data: alertsData, diff --git a/src/platform/packages/shared/response-ops/alerts-table/types.ts b/src/platform/packages/shared/response-ops/alerts-table/types.ts index 635fba330c5d2..18d14affec46b 100644 --- a/src/platform/packages/shared/response-ops/alerts-table/types.ts +++ b/src/platform/packages/shared/response-ops/alerts-table/types.ts @@ -373,6 +373,7 @@ export interface PublicAlertsDataGridProps > { ruleTypeIds: string[]; minScore?: number; + trackScores?: boolean; consumers?: string[]; /** * If true, shows a button in the table toolbar to inspect the search alerts request diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx index ebe0cb12e83d8..dfe8894d93a07 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx @@ -66,6 +66,7 @@ export function RelatedAlertsTable({ alertData }: Props) { columns={columns} ruleTypeIds={OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES} minScore={1.5} + trackScores={true} initialSort={initialSort} casesConfiguration={caseConfiguration} additionalContext={{ From 32ec62b2c96a83572a5a4af655ab49009dcdf470 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Mon, 7 Apr 2025 10:47:34 -0400 Subject: [PATCH 25/36] adjust copy --- .../components/related_alerts/get_related_columns.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx index a072c995ef0fc..947d5fbdfa08f 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx @@ -46,7 +46,7 @@ export const getRelatedColumns = (): EuiDataGridColumn[] => { { id: ALERT_CASE_IDS, displayAsText: i18n.translate('xpack.observability.alertsTGrid.caseIdsColumnDescription', { - defaultMessage: 'Related cases', + defaultMessage: 'Attached cases', }), }, { From 1723ed30ca4e9ce086484347cee97f5bace7412a Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Tue, 8 Apr 2025 11:43:45 -0400 Subject: [PATCH 26/36] Ensure individual score results is greater than 0 --- .../hooks/related_alerts/use_build_related_alerts_query.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts index 4bcff431a8d5d..fff62250ba018 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.ts @@ -132,8 +132,8 @@ export function useBuildRelatedAlertsQuery({ alert }: Props): QueryDslQueryConta return (double) intersection.size() / union.size(); } Set tagsQuery = new HashSet(params.tags); - Set tagsDoc = new HashSet(doc.containsKey('tags.keyword') ? doc['tags.keyword'].values : []); - return jaccardSimilarity(tagsQuery, tagsDoc); + Set tagsDoc = new HashSet(doc.containsKey("kibana.alert.rule.tags") && !doc.get("kibana.alert.rule.tags").empty ? doc.get("kibana.alert.rule.tags") : []); + return 1.0 + jaccardSimilarity(tagsQuery, tagsDoc); `), params: { tags, @@ -166,7 +166,7 @@ export function useBuildRelatedAlertsQuery({ alert }: Props): QueryDslQueryConta } } - return jaccardSimilarity(instanceIdQuery, instanceIdDoc); + return 1.0 + jaccardSimilarity(instanceIdQuery, instanceIdDoc); `), params: { instanceId, @@ -176,6 +176,7 @@ export function useBuildRelatedAlertsQuery({ alert }: Props): QueryDslQueryConta weight: 5, }, ], + score_mode: 'multiply', boost_mode: 'sum', }, }, From 831b9e7533fa87ce7b5f9c770f9ff27dfd86f639 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Wed, 9 Apr 2025 10:15:24 -0400 Subject: [PATCH 27/36] adjust trackScores and add a test --- .../server/search_strategy/search_strategy.ts | 1 + .../tests/basic/search_strategy.ts | 99 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.ts b/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.ts index 9057042abd5db..8e7cf55545428 100644 --- a/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.ts +++ b/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.ts @@ -173,6 +173,7 @@ export const ruleRegistrySearchStrategyProvider = ( query, ...(request.runtimeMappings ? { runtime_mappings: request.runtimeMappings } : {}), ...(request.minScore ? { min_score: request.minScore } : {}), + ...(request.trackScores ? { track_scores: request.trackScores } : {}), }, }; return (isAnyRuleTypeESAuthorized ? requestUserEs : internalUserEs).search( diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts index 31cfe83824dc9..6caee3b1acdb2 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts @@ -770,11 +770,110 @@ export default ({ getService }: FtrProviderContext) => { strategy: 'privateRuleRegistryAlertsSearchStrategy', space: 'default', }); + // console.log('hits', resultWithMinScore.rawResponse.hits.hits); + + const allScores = resultWithMinScore.rawResponse.hits.hits.map((hit) => { + return hit._score; + }); + allScores.forEach((score) => expect(score >= 11).to.be(true)); expect(resultWithMinScore.rawResponse.hits.total).to.eql(8); validateRuleTypeIds(resultWithMinScore, apmRuleTypeIds); }); + + it('should track scores alerts when sorting when trackScores is true', async () => { + const query = { + bool: { + filter: [], + should: [ + { + function_score: { + functions: [ + { + exp: { + [ALERT_START]: { + origin: '2021-10-19T14:58:08.539Z', + scale: '10m', + offset: '10m', + decay: 0.5, + }, + }, + weight: 10, + }, + ], + boost_mode: 'sum', + }, + }, + ], + must: [], + must_not: [], + }, + }; + const resultWithoutTrackScore = await secureSearch.send({ + supertestWithoutAuth, + auth: { + username: obsOnlySpacesAll.username, + password: obsOnlySpacesAll.password, + }, + referer: 'test', + kibanaVersion, + internalOrigin: 'Kibana', + options: { + ruleTypeIds: apmRuleTypeIds, + query, + sort: [ + { + 'kibana.alert.start': { + order: 'desc', + }, + }, + ], + minScore: 11, + }, + strategy: 'privateRuleRegistryAlertsSearchStrategy', + space: 'default', + }); + + resultWithoutTrackScore.rawResponse.hits.hits.forEach((hit) => { + expect(hit._score).to.be(null); + }); + + validateRuleTypeIds(resultWithoutTrackScore, apmRuleTypeIds); + + const resultWithTrackScore = await secureSearch.send({ + supertestWithoutAuth, + auth: { + username: obsOnlySpacesAll.username, + password: obsOnlySpacesAll.password, + }, + referer: 'test', + kibanaVersion, + internalOrigin: 'Kibana', + options: { + ruleTypeIds: apmRuleTypeIds, + query, + sort: [ + { + 'kibana.alert.start': { + order: 'desc', + }, + }, + ], + minScore: 11, + trackScores: true, + }, + strategy: 'privateRuleRegistryAlertsSearchStrategy', + space: 'default', + }); + + resultWithTrackScore.rawResponse.hits.hits.forEach((hit) => { + expect(hit._score).not.to.be(null); + expect(hit._score).to.be(11); + }); + + validateRuleTypeIds(resultWithTrackScore, apmRuleTypeIds); + }); }); describe('discover', () => { From d341b70f2ac3faeb544ce607916f8293825c342e Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Wed, 9 Apr 2025 12:13:58 -0400 Subject: [PATCH 28/36] adjust sizing of actions column and prevent sorting actions on individual columns --- .../alert_actions/alert_actions.tsx | 7 +++-- .../alerts_table/common/cell_value.tsx | 8 ++++-- .../related_alerts/get_related_columns.tsx | 28 ++++++++++++++----- .../related_alerts/relation_col.tsx | 2 +- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index bdd67f53c31ca..811317b4d74bf 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -33,7 +33,7 @@ import { } from '../..'; import { ALERT_DETAILS_PAGE_ID } from '../../pages/alert_details/alert_details'; -export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> = ({ +export function AlertActions({ observabilityRuleTypeRegistry, alert, tableId, @@ -41,7 +41,7 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> openAlertInFlyout, parentAlert, ...rest -}) => { +}: GetObservabilityAlertsTableProp<'renderActionsCell'>) { const services = useKibana().services; const { http: { @@ -214,6 +214,7 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> css={{ textAlign: 'center', }} + grow={false} > ); -}; +} // Default export used for lazy loading // eslint-disable-next-line import/no-default-export diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx index 724bde5cc118d..c11163c5c5694 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alerts_table/common/cell_value.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiLink, EuiText } from '@elastic/eui'; +import { EuiLink, EuiText, EuiFlexGroup } from '@elastic/eui'; import React, { ReactNode } from 'react'; import { ALERT_DURATION, @@ -140,7 +140,11 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa return {val}; }, [RELATED_ACTIONS_COL]: (val) => { - return ; + return ( + + + + ); }, [ALERT_CASE_IDS]: (value) => { return <>{value}; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx index 947d5fbdfa08f..3070ac5806d3f 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx @@ -16,45 +16,59 @@ export const RELATED_ACTIONS_COL = 'relatedActions'; export const getRelatedColumns = (): EuiDataGridColumn[] => { return [ { + id: ALERT_STATUS, displayAsText: i18n.translate('xpack.observability.alertsTGrid.statusColumnDescription', { defaultMessage: 'Alert Status', }), - id: ALERT_STATUS, initialWidth: 120, + isSortable: false, + actions: false, }, { + id: ALERT_RULE_NAME, displayAsText: i18n.translate('xpack.observability.alertsTGrid.ruleNameColumnDescription', { defaultMessage: 'Rule name', }), - id: ALERT_RULE_NAME, initialWidth: 250, + isSortable: false, + actions: false, }, { + id: RELATED_ALERT_REASON, displayAsText: i18n.translate('xpack.observability.alertsTGrid.reasonDescription', { defaultMessage: 'Reason', }), - id: RELATED_ALERT_REASON, - initialWidth: 300, + initialWidth: 400, + isSortable: false, + actions: false, }, { + id: RELATION_COL, displayAsText: i18n.translate('xpack.observability.alertsTGrid.relationColumnDescription', { defaultMessage: 'Relation', }), - id: RELATION_COL, initialWidth: 350, + isSortable: false, + actions: false, }, { id: ALERT_CASE_IDS, displayAsText: i18n.translate('xpack.observability.alertsTGrid.caseIdsColumnDescription', { defaultMessage: 'Attached cases', }), + initialWidth: 150, + isSortable: false, + actions: false, }, { + id: RELATED_ACTIONS_COL, displayAsText: i18n.translate('xpack.observability.alertsTGrid.actionsColumnDescription', { defaultMessage: 'Actions', }), - id: RELATED_ACTIONS_COL, - initialWidth: 120, + initialWidth: 75, + isResizable: false, + isSortable: false, + actions: false, }, ]; }; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx index deb574c71099f..8a71ef55ec79e 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx @@ -72,7 +72,7 @@ export function RelationCol({ alert, parentAlert }: { alert: Alert; parentAlert: ); From f27601b3f98fec2ccbd66478fd68c97254b827a2 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Wed, 9 Apr 2025 13:02:48 -0400 Subject: [PATCH 29/36] adjust types --- .../public/components/alert_actions/alert_actions.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index 811317b4d74bf..69bb6a00d157e 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -33,7 +33,7 @@ import { } from '../..'; import { ALERT_DETAILS_PAGE_ID } from '../../pages/alert_details/alert_details'; -export function AlertActions({ +export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> = ({ observabilityRuleTypeRegistry, alert, tableId, @@ -41,7 +41,7 @@ export function AlertActions({ openAlertInFlyout, parentAlert, ...rest -}: GetObservabilityAlertsTableProp<'renderActionsCell'>) { +}) => { const services = useKibana().services; const { http: { @@ -244,7 +244,7 @@ export function AlertActions({ ); -} +}; // Default export used for lazy loading // eslint-disable-next-line import/no-default-export From e0f6dab013072d4505b1d6e575baaea19bb0d234 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Wed, 9 Apr 2025 15:59:49 -0400 Subject: [PATCH 30/36] adjust types --- .../security_and_spaces/tests/basic/search_strategy.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts index 6caee3b1acdb2..83806d2750b75 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts @@ -770,12 +770,17 @@ export default ({ getService }: FtrProviderContext) => { strategy: 'privateRuleRegistryAlertsSearchStrategy', space: 'default', }); - // console.log('hits', resultWithMinScore.rawResponse.hits.hits); const allScores = resultWithMinScore.rawResponse.hits.hits.map((hit) => { return hit._score; }); - allScores.forEach((score) => expect(score >= 11).to.be(true)); + allScores.forEach((score) => { + if (score) { + expect(score >= 11).to.be(true); + } else { + throw new Error('Score is null'); + } + }); expect(resultWithMinScore.rawResponse.hits.total).to.eql(8); From 0641f854317557cd02df9cc099b49887eca5106d Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Wed, 9 Apr 2025 23:00:41 -0400 Subject: [PATCH 31/36] adjust css for alert actions --- .../public/components/alert_actions/alert_actions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index 69bb6a00d157e..2f251526a9821 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx @@ -214,7 +214,7 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> css={{ textAlign: 'center', }} - grow={false} + grow={parentAlert ? false : undefined} > Date: Thu, 10 Apr 2025 10:14:33 -0400 Subject: [PATCH 32/36] adjust types --- .../detections/components/alert_summary/table/render_cell.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.tsx index 313f73de634f0..20c87b3781e60 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.tsx @@ -30,7 +30,7 @@ export interface CellValueProps { */ export const CellValue = memo(({ alert, columnId }: CellValueProps) => { const displayValue: string | null = useMemo(() => { - const cellValues: string | JsonValue[] = alert[columnId]; + const cellValues: string | number | JsonValue[] = alert[columnId]; // Displays string as is. // Joins values of array with more than one element. From 14cc8a00b0c83bd7abd14b16c20096fc8786ae63 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Thu, 10 Apr 2025 10:31:01 -0400 Subject: [PATCH 33/36] Remove newline at end of kibana.jsonc file From 5bc4667bda07fb1a6dc7fb99549dfdde04a91872 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Thu, 10 Apr 2025 10:33:12 -0400 Subject: [PATCH 34/36] remove whitespace --- .../solutions/observability/plugins/observability/kibana.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/observability/kibana.jsonc b/x-pack/solutions/observability/plugins/observability/kibana.jsonc index e83a03dadd23d..4a83799bfdb6e 100644 --- a/x-pack/solutions/observability/plugins/observability/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability/kibana.jsonc @@ -72,4 +72,4 @@ "common" ] } -} +} \ No newline at end of file From 0e98a91fd3fab2e579c61a0f0f400ba7cab2a9bc Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Thu, 10 Apr 2025 10:39:47 -0400 Subject: [PATCH 35/36] handle number type in cell renders --- .../alert_summary/table/render_cell.test.tsx | 17 +++++++++++++++++ .../alert_summary/table/render_cell.tsx | 2 ++ 2 files changed, 19 insertions(+) diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.test.tsx index 1940b669b72ec..9a4d46e532416 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.test.tsx @@ -47,6 +47,23 @@ describe('CellValue', () => { expect(getByText('value1')).toBeInTheDocument(); }); + it('should handle a number value', () => { + const alert: Alert = { + _id: '_id', + _index: '_index', + field1: 123, + }; + const columnId = 'field1'; + + const { getByText } = render( + + + + ); + + expect(getByText('123')).toBeInTheDocument(); + }); + it('should handle array of booleans', () => { const alert: Alert = { _id: '_id', diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.tsx index 20c87b3781e60..080b849d7edb4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.tsx @@ -38,6 +38,8 @@ export const CellValue = memo(({ alert, columnId }: CellValueProps) => { // Return the string of the value otherwise. if (typeof cellValues === 'string') { return cellValues; + } else if (typeof cellValues === 'number') { + return cellValues.toString(); } else if (Array.isArray(cellValues)) { if (cellValues.length > 1) { return cellValues.join(', '); From b6f1c868fba08cdaa92469b5497d22ea23a8fd58 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Sun, 13 Apr 2025 14:46:35 -0400 Subject: [PATCH 36/36] add unit test --- .../search_strategy/search_strategy.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.test.ts b/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.test.ts index 5ed835e18d851..179d9f08495b8 100644 --- a/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.test.ts +++ b/x-pack/platform/plugins/shared/rule_registry/server/search_strategy/search_strategy.test.ts @@ -774,4 +774,49 @@ describe('ruleRegistrySearchStrategyProvider()', () => { }) ); }); + + it('passes track_scores if trackScores is provided', async () => { + const request: RuleRegistrySearchRequest = { + ruleTypeIds: ['siem.esqlRule'], + trackScores: true, + }; + const options = {}; + const deps = { + request: {}, + }; + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['security-siem']); + + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); + + await lastValueFrom( + strategy.search(request, options, deps as unknown as SearchStrategyDependencies) + ); + + const arg0 = searchStrategySearch.mock.calls[0][0]; + + expect(arg0).toEqual( + expect.objectContaining({ + id: undefined, + params: expect.objectContaining({ + allow_no_indices: true, + body: expect.objectContaining({ + _source: false, + fields: expect.arrayContaining([ + expect.objectContaining({ + field: '@timestamp', + include_unmapped: true, + }), + ]), + from: 0, + size: 1000, + sort: [], + track_scores: true, + }), + ignore_unavailable: true, + index: ['security-siem'], + }), + }) + ); + }); });