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 ? (
-
) : 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 ? (
-
+
) : 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'],
+ }),
+ })
+ );
+ });
});