From be1926517efd34657a65b6caabf9394ce873806a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 14 Apr 2025 17:35:40 +0200 Subject: [PATCH 1/5] [Observability] Related alerts based on scoring !! (#215673) ## Summary Copying most of https://github.com/elastic/kibana/pull/214017 !! Fixes https://github.com/elastic/kibana/issues/214372 ### Implementation We are now using response ops alerts table with custom score querying based on tags/groups matches and Jaccard similarity on documents !! image --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dominique Clarke Co-authored-by: Kevin Delemme (cherry picked from commit 760106eb8611a934d10157fb021979637d49dae4) # Conflicts: # x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx # x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.test.tsx # x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.tsx # x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.test.tsx # x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/table/render_cell.tsx --- .../shared/kbn-alerting-types/alerts_types.ts | 3 +- .../search_strategy_types.ts | 2 + .../apis/search_alerts/search_alerts.test.tsx | 12 + .../apis/search_alerts/search_alerts.ts | 29 +- .../hooks/use_search_alerts_query.test.tsx | 12 + .../common/hooks/use_search_alerts_query.ts | 4 + .../alerts-table/components/alerts_table.tsx | 8 +- .../components/default_alerts_flyout.tsx | 3 +- .../components/default_cell_value.tsx | 2 +- .../shared/response-ops/alerts-table/types.ts | 2 + .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../search_strategy/search_strategy.test.ts | 103 ++++++++ .../server/search_strategy/search_strategy.ts | 2 + .../public/application/index.tsx | 7 +- .../alert_actions/alert_actions.test.tsx | 31 ++- .../alert_actions/alert_actions.tsx | 248 ++++++------------ .../alert_actions/use_case_actions.ts | 69 +++++ .../alerts_table/common/cell_tooltip.tsx | 2 +- .../alerts_table/common/cell_value.test.tsx | 2 + .../alerts_table/common/cell_value.tsx | 108 ++++++-- .../alerts_table/common/get_columns.tsx | 2 +- .../public/components/alerts_table/types.ts | 3 +- .../plugins/observability/public/index.ts | 3 - .../pages/alert_details/alert_details.tsx | 28 +- .../components/inspector_header_link.tsx | 32 +++ .../components/related_alerts.test.tsx | 69 ----- .../components/related_alerts.tsx | 221 ---------------- .../related_alerts/get_related_columns.tsx | 74 ++++++ .../related_alerts/related_alerts.tsx | 23 ++ .../related_alerts/related_alerts_table.tsx | 98 +++++++ .../related_alerts/relation_col.tsx | 79 ++++++ .../use_build_related_alerts_query.ts | 186 +++++++++++++ .../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 + .../plugins/observability/tsconfig.json | 1 - .../default_status_alert.journey.ts | 3 +- .../tests/basic/search_strategy.ts | 189 +++++++++++++ 42 files changed, 1140 insertions(+), 623 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/get_related_columns.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_table.tsx create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx create mode 100644 x-pack/solutions/observability/plugins/observability/public/pages/alert_details/hooks/related_alerts/use_build_related_alerts_query.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 4346837c79da2..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,6 +13,7 @@ import type { JsonValue } from '@kbn/utility-types'; export interface MetaAlertFields { _id: string; _index: string; + _score?: number; } export interface LegacyField { @@ -29,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/kbn-alerting-types/search_strategy_types.ts b/src/platform/packages/shared/kbn-alerting-types/search_strategy_types.ts index 3568882d6f891..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 @@ -24,6 +24,8 @@ export type RuleRegistrySearchRequest = IEsSearchRequest & { sort?: SortCombinations[]; 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.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' }, ], ], 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..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 @@ -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,14 @@ export interface SearchAlertsParams { * The page size to fetch */ pageSize: number; + /** + * The minimum score to apply to the query + */ + minScore?: number; + /** + * Whether to track the score of the query + */ + trackScores?: boolean; } export interface SearchAlertsResult { @@ -92,6 +100,8 @@ export const searchAlerts = ({ runtimeMappings, pageIndex, pageSize, + minScore, + trackScores, }: SearchAlertsParams): Promise => lastValueFrom( data.search @@ -104,6 +114,8 @@ export const searchAlerts = ({ pagination: { pageIndex, pageSize }, sort, runtimeMappings, + minScore, + trackScores, }, { strategy: 'privateRuleRegistryAlertsSearchStrategy', @@ -167,6 +179,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.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' }, ], ], 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 bbd1463442f0c..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 @@ -43,6 +43,8 @@ export const useSearchAlertsQuery = ({ data, ...params }: UseSearchAlertsQueryPa runtimeMappings, pageIndex = 0, pageSize = DEFAULT_ALERTS_PAGE_SIZE, + minScore, + trackScores, } = params; return useQuery({ queryKey: queryKeyPrefix.concat(JSON.stringify(params)), @@ -58,6 +60,8 @@ export const useSearchAlertsQuery = ({ data, ...params }: UseSearchAlertsQueryPa runtimeMappings, 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 1f5c413d0117b..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 @@ -170,6 +170,8 @@ const AlertsTableContent = typedForwardRef( ruleTypeIds, consumers, query, + minScore, + trackScores = false, initialSort = DEFAULT_SORT, initialPageSize = DEFAULT_ALERTS_PAGE_SIZE, leadingControlColumns = DEFAULT_LEADING_CONTROL_COLUMNS, @@ -277,6 +279,8 @@ const AlertsTableContent = typedForwardRef( runtimeMappings, pageIndex: 0, pageSize: initialPageSize, + minScore, + trackScores, }); useEffect(() => { @@ -287,6 +291,8 @@ const AlertsTableContent = typedForwardRef( query, sort, runtimeMappings, + minScore, + trackScores, // Go back to the first page if the query changes pageIndex: !deepEqual(prevQueryParams, { ruleTypeIds, @@ -300,7 +306,7 @@ const AlertsTableContent = typedForwardRef( : oldPageIndex, pageSize: oldPageSize, })); - }, [ruleTypeIds, fields, query, runtimeMappings, sort, consumers]); + }, [ruleTypeIds, fields, query, runtimeMappings, sort, consumers, minScore, trackScores]); const { data: alertsData, 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/src/platform/packages/shared/response-ops/alerts-table/types.ts b/src/platform/packages/shared/response-ops/alerts-table/types.ts index 4833d03394246..18d14affec46b 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,8 @@ export interface PublicAlertsDataGridProps | 'columns' > { 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/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 90ad3e79ba546..47a74a99abe99 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -35139,8 +35139,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 3dfc0d44754c3..0531f7d4861e0 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -35113,8 +35113,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 31f212ca66d2b..e24c293180c61 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -35178,8 +35178,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 企业协议,请输入您的折扣率以更新分析成本计算。", 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..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 @@ -716,4 +716,107 @@ 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'], + }), + }) + ); + }); + + 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'], + }), + }) + ); + }); }); 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 0c28d71517c0d..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 @@ -172,6 +172,8 @@ export const ruleRegistrySearchStrategyProvider = ( from: request.pagination ? request.pagination.pageIndex * size : 0, 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/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.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/alert_actions/alert_actions.tsx b/x-pack/solutions/observability/plugins/observability/public/components/alert_actions/alert_actions.tsx index 9da3b598513ff..f78bc0c19c3e3 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,85 +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 type { EventNonEcsData } from '../../../common/typings'; -import { GetObservabilityAlertsTableProp } from '../alerts_table/types'; +import { ALERT_UUID } from '@kbn/rule-data-utils'; +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 { + GetObservabilityAlertsTableProp, + ObservabilityAlertsTableContext, + observabilityFeatureId, +} 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, 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, + parentAlert, + ...rest }) => { - const { services } = useAlertsTableContext(); + 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 isInApp = Boolean(tableId === SLO_ALERTS_TABLE_ID && isSLODetailsPage); - const ecsData = useMemo( - () => ({ - _id: alert._id, - _index: alert._index, - }), - [alert._id, alert._index] - ); const userCasesPermissions = canUseCases([observabilityFeatureId]); const [viewInAppUrl, setViewInAppUrl] = useState(); @@ -124,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, + alerts: [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 ? [ @@ -271,7 +123,38 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> , ] : []), - defaultRowActions, + useMemo( + () => ( + + observabilityRuleTypeRegistry={observabilityRuleTypeRegistry} + key="defaultRowActions" + onActionExecuted={closeActionsPopover} + isAlertDetailsEnabled={true} + resolveRulePagePath={(ruleId, currentPageId) => + 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} + {...rest} + /> + ), + [ + alert, + closeActionsPopover, + observabilityRuleTypeRegistry, + openAlertInFlyout, + refresh, + rest, + tableId, + ] + ), ]; const actionsToolTip = @@ -283,9 +166,29 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> defaultMessage: 'More actions', }); + const onExpandEvent = () => { + const parsedAlert = parseAlert(observabilityRuleTypeRegistry)(alert); + openAlertInFlyout?.(parsedAlert.fields[ALERT_UUID]); + }; + + const hideViewInApp = isInApp || viewInAppUrl === '' || parentAlert; + return ( <> - {viewInAppUrl !== '' && !isInApp ? ( + {!parentAlert && ( + + + + + + )} + {!hideViewInApp && ( /> - ) : null} + )} 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 }); + + 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: getCaseAttachments() }); + closeActionsPopover(); + }; + + const handleAddToExistingCaseClick = () => { + selectCaseModal.open({ getAttachments: () => getCaseAttachments() }); + closeActionsPopover(); + }; + + return { + isPopoverOpen, + setIsPopoverOpen, + handleAddToExistingCaseClick, + handleAddToNewCaseClick, + }; +}; 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.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; 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..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,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiLink } from '@elastic/eui'; -import React from 'react'; +import { EuiLink, EuiText, EuiFlexGroup } from '@elastic/eui'; +import React, { ReactNode } from 'react'; import { ALERT_DURATION, ALERT_SEVERITY, @@ -21,20 +21,31 @@ import { ALERT_RULE_CATEGORY, 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'; +import type { JsonValue } from '@kbn/utility-types'; +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'; import { AlertStatusIndicator } from '../../alert_status_indicator'; import { parseAlert } from '../../../pages/alerts/helpers/parse_alert'; import { CellTooltip } from './cell_tooltip'; import { TimestampTooltip } from './timestamp_tooltip'; -import type { GetObservabilityAlertsTableProp } from '../types'; +import { GetObservabilityAlertsTableProp } from '../types'; +import AlertActions from '../../alert_actions/alert_actions'; -const getAlertFieldValue = (alert: Alert, fieldName: string) => { +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)) { @@ -51,40 +62,49 @@ const getAlertFieldValue = (alert: Alert, fieldName: string) => { return '--'; }; +export type AlertCellRenderers = Record 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, -}) => { - const value = getAlertFieldValue(alert, columnId); +export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellValue'> = (props) => { + const { + columnId, + alert, + openAlertInFlyout, + observabilityRuleTypeRegistry, + services: { http }, + parentAlert, + } = 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 ( @@ -96,10 +116,42 @@ export const AlertsTableCellValue: GetObservabilityAlertsTableProp<'renderCellVa {parsedAlert.reason} ); - case ALERT_RULE_NAME: + }, + [ALERT_RULE_NAME]: (value) => { const ruleCategory = getAlertFieldValue(alert, ALERT_RULE_CATEGORY); - return ; - default: + const ruleId = getAlertFieldValue(alert, ALERT_RULE_UUID); + const ruleLink = ruleId ? http.basePath.prepend(paths.observability.ruleDetails(ruleId)) : ''; + return ( + + {value} + + } + tooltipContent={ruleCategory} + /> + ); + }, + [RELATION_COL]: (value) => { + return ; + }, + [RELATED_ALERT_REASON]: (value) => { + const val = getAlertFieldValue(alert, ALERT_REASON); + return {val}; + }, + [RELATED_ACTIONS_COL]: (val) => { + return ( + + + + ); + }, + [ALERT_CASE_IDS]: (value) => { return <>{value}; - } + }, + }; + + const val = getAlertFieldValue(alert, columnId); + + 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 382dacb163af5..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 @@ -7,11 +7,12 @@ 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 '../..'; export interface ObservabilityAlertsTableContext { observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; config: ConfigSchema; + parentAlert?: TopAlert; } export type ObservabilityAlertsTableProps = SetOptional< diff --git a/x-pack/solutions/observability/plugins/observability/public/index.ts b/x-pack/solutions/observability/plugins/observability/public/index.ts index 890ef456b6e8e..d93eeae337f1a 100644 --- a/x-pack/solutions/observability/plugins/observability/public/index.ts +++ b/x-pack/solutions/observability/plugins/observability/public/index.ts @@ -8,7 +8,6 @@ // TODO: https://github.com/elastic/kibana/issues/110905 import { PluginInitializer, PluginInitializerContext } from '@kbn/core/public'; -import { lazy } from 'react'; import { Plugin, ObservabilityPublicPluginsStart, @@ -58,8 +57,6 @@ export { getCoreVitalsComponent } from './pages/overview/components/sections/ux/ export { ObservabilityAlertSearchBar } from './components/alert_search_bar/get_alert_search_bar_lazy'; export { DatePicker } from './pages/overview/components/date_picker'; -export const LazyAlertsFlyout = lazy(() => 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 d51513240ac23..0000000000000 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.test.tsx +++ /dev/null @@ -1,69 +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 = () => { - useKibanaMock.mockReturnValue({ - services: { - ...kibanaStartMock.startContract().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 82f558ec28e46..0000000000000 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts.tsx +++ /dev/null @@ -1,221 +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, type Query } 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 { 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; -} - -const defaultState: AlertSearchBarContainerState = { ...DEFAULT_STATE, status: 'active' }; -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 defaultQuery = useRef([ - { query: `not kibana.alert.uuid: ${alertId}`, language: 'kuery' }, - ]); - - 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] ?? 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/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..3070ac5806d3f --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/get_related_columns.tsx @@ -0,0 +1,74 @@ +/* + * 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_CASE_IDS, ALERT_RULE_NAME, ALERT_STATUS } from '@kbn/rule-data-utils'; +import { i18n } from '@kbn/i18n'; + +export const RELATED_ALERT_REASON = 'relatedAlertReason'; +export const RELATION_COL = 'relatedRelation'; +export const RELATED_ACTIONS_COL = 'relatedActions'; + +export const getRelatedColumns = (): EuiDataGridColumn[] => { + return [ + { + id: ALERT_STATUS, + displayAsText: i18n.translate('xpack.observability.alertsTGrid.statusColumnDescription', { + defaultMessage: 'Alert Status', + }), + initialWidth: 120, + isSortable: false, + actions: false, + }, + { + id: ALERT_RULE_NAME, + displayAsText: i18n.translate('xpack.observability.alertsTGrid.ruleNameColumnDescription', { + defaultMessage: 'Rule name', + }), + initialWidth: 250, + isSortable: false, + actions: false, + }, + { + id: RELATED_ALERT_REASON, + displayAsText: i18n.translate('xpack.observability.alertsTGrid.reasonDescription', { + defaultMessage: 'Reason', + }), + initialWidth: 400, + isSortable: false, + actions: false, + }, + { + id: RELATION_COL, + displayAsText: i18n.translate('xpack.observability.alertsTGrid.relationColumnDescription', { + defaultMessage: 'Relation', + }), + 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', + }), + initialWidth: 75, + isResizable: false, + isSortable: false, + actions: false, + }, + ]; +}; 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..ecb0213c15948 --- /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 { RelatedAlertsTable } from './related_alerts_table'; +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_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..dfe8894d93a07 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/related_alerts_table.tsx @@ -0,0 +1,98 @@ +/* + * 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 { 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'; +import { useBuildRelatedAlertsQuery } from '../../hooks/related_alerts/use_build_related_alerts_query'; +import { AlertData } from '../../../../hooks/use_fetch_alert_detail'; +import { + GetObservabilityAlertsTableProp, + ObservabilityAlertsTableContext, + observabilityFeatureId, +} 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 { casesFeatureIdV2 } from '../../../../../common'; + +interface Props { + alertData: AlertData; +} + +const columns = getRelatedColumns(); +const initialSort: Array> = [ + { + _score: 'desc', + }, + { + [ALERT_START]: 'desc', + }, + { + [ALERT_UUID]: 'desc', + }, +]; + +const caseConfiguration: GetObservabilityAlertsTableProp<'casesConfiguration'> = { + featureId: casesFeatureIdV2, + owner: [observabilityFeatureId], +}; + +const RELATED_ALERTS_TABLE_ID = 'xpack.observability.alerts.relatedAlerts'; + +export function RelatedAlertsTable({ alertData }: Props) { + const { formatted: alert } = alertData; + const esQuery = useBuildRelatedAlertsQuery({ alert }); + const { observabilityRuleTypeRegistry, config } = usePluginContext(); + + const services = useKibana().services; + + return ( + + + + id={RELATED_ALERTS_TABLE_ID} + query={esQuery} + columns={columns} + ruleTypeIds={OBSERVABILITY_RULE_TYPE_IDS_WITH_SUPPORTED_STACK_RULE_TYPES} + minScore={1.5} + trackScores={true} + initialSort={initialSort} + casesConfiguration={caseConfiguration} + additionalContext={{ + observabilityRuleTypeRegistry, + config, + parentAlert: alert, + }} + toolbarVisibility={{ + showSortSelector: false, + }} + renderCellValue={AlertsTableCellValue} + renderFlyoutBody={AlertsFlyoutBody} + renderFlyoutFooter={AlertsFlyoutFooter} + showAlertStatusWithFlapping + services={services} + gridStyle={{ + border: 'horizontal', + header: 'underline', + cellPadding: 'l', + fontSize: 'm', + }} + rowHeightsOptions={{ + defaultHeight: 'auto', + }} + height="600px" + /> + + ); +} 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..8a71ef55ec79e --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/components/related_alerts/relation_col.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; 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, + ALERT_RULE_NAME, +} from '@kbn/rule-data-utils'; +import { intersection } from 'lodash'; +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 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 = ruleId === 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: ( + + {ruleName} + + ), + }); + } + 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..fff62250ba018 --- /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,186 @@ +/* + * 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("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, + }, + }, + }, + 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 1.0 + jaccardSimilarity(instanceIdQuery, instanceIdDoc); + `), + params: { + instanceId, + }, + }, + }, + weight: 5, + }, + ], + score_mode: 'multiply', + boost_mode: 'sum', + }, + }, + ], + }, + }; +} 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 d9c310cf4fcf2..a95cd43ff96ad 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; diff --git a/x-pack/solutions/observability/plugins/observability/tsconfig.json b/x-pack/solutions/observability/plugins/observability/tsconfig.json index 99f128eeb7395..1d117152193e3 100644 --- a/x-pack/solutions/observability/plugins/observability/tsconfig.json +++ b/x-pack/solutions/observability/plugins/observability/tsconfig.json @@ -78,7 +78,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", 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 44b69e9fb7104..aa08ad024e647 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); }); 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 78ef1da93cec1..a05c9f2241bcc 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,194 @@ 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', + }); + + const allScores = resultWithMinScore.rawResponse.hits.hits.map((hit) => { + return hit._score; + }); + 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); + + 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', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); From 1a81170627e520086634f128d6e2e619aa138f85 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Tue, 15 Apr 2025 10:57:41 -0400 Subject: [PATCH 2/5] add view details i18n --- .../public/components/alert_actions/alert_actions.tsx | 4 ++++ 1 file changed, 4 insertions(+) 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 f78bc0c19c3e3..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 @@ -250,4 +250,8 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> // eslint-disable-next-line import/no-default-export export default AlertActions; +const VIEW_DETAILS = i18n.translate('xpack.observability.alertsTable.viewDetailsTextLabel', { + defaultMessage: 'Alert details', +}); + export type AlertActions = typeof AlertActions; From 63ddb74430c34e3d0d16040505823bc1f9170d79 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Tue, 15 Apr 2025 13:07:37 -0400 Subject: [PATCH 3/5] adjust tests --- .../security_and_spaces/tests/basic/search_strategy.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 a05c9f2241bcc..cb5083d63203f 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 @@ -732,7 +732,7 @@ export default ({ getService }: FtrProviderContext) => { must_not: [], }, }; - const resultWithoutMinScore = await secureSearch.send({ + const resultWithoutMinScore = await secureBsearch.send({ supertestWithoutAuth, auth: { username: obsOnlySpacesAll.username, @@ -753,7 +753,7 @@ export default ({ getService }: FtrProviderContext) => { validateRuleTypeIds(resultWithoutMinScore, apmRuleTypeIds); - const resultWithMinScore = await secureSearch.send({ + const resultWithMinScore = await secureBsearch.send({ supertestWithoutAuth, auth: { username: obsOnlySpacesAll.username, @@ -815,7 +815,7 @@ export default ({ getService }: FtrProviderContext) => { must_not: [], }, }; - const resultWithoutTrackScore = await secureSearch.send({ + const resultWithoutTrackScore = await secureBsearch.send({ supertestWithoutAuth, auth: { username: obsOnlySpacesAll.username, @@ -846,7 +846,7 @@ export default ({ getService }: FtrProviderContext) => { validateRuleTypeIds(resultWithoutTrackScore, apmRuleTypeIds); - const resultWithTrackScore = await secureSearch.send({ + const resultWithTrackScore = await secureBsearch.send({ supertestWithoutAuth, auth: { username: obsOnlySpacesAll.username, From dc82c896635ff3b917abd438ea873ff9ee601af7 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Fri, 18 Apr 2025 13:48:26 -0400 Subject: [PATCH 4/5] adjust jest tests --- .../public/pages/alert_details/alert_details.test.tsx | 6 +++++- .../observability/public/pages/alerts/alerts.test.tsx | 6 +++++- .../plugins/observability/public/pages/rules/rules.test.tsx | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.test.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.test.tsx index 17b13cc6f140f..9774a9dc639e2 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.test.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alert_details/alert_details.test.tsx @@ -83,7 +83,11 @@ jest.mock('../../hooks/use_fetch_rule', () => { }), }; }); -jest.mock('@kbn/observability-shared-plugin/public'); +jest.mock('@kbn/observability-shared-plugin/public', () => ({ + ...jest.requireActual('@kbn/observability-shared-plugin/public'), + useBreadcrumbs: jest.fn(), + TagsList: jest.fn(), +})); jest.mock('@kbn/ebt-tools'); const usePerformanceContextMock = usePerformanceContext as jest.Mock; diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/alerts/alerts.test.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/alerts/alerts.test.tsx index 0b5b57c6b5362..61e6adad7abd2 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/alerts/alerts.test.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/alerts/alerts.test.tsx @@ -65,7 +65,11 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({ __esModule: true, useKibana: jest.fn(() => mockUseKibanaReturnValue), })); -jest.mock('@kbn/observability-shared-plugin/public'); +jest.mock('@kbn/observability-shared-plugin/public', () => ({ + ...jest.requireActual('@kbn/observability-shared-plugin/public'), + useBreadcrumbs: jest.fn(), + TagsList: jest.fn(), +})); jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ appMountParameters: { setHeaderActionMenu: () => {}, diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.test.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.test.tsx index 4f0e2e4f48632..2e308e3c0ff4d 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.test.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/rules/rules.test.tsx @@ -54,7 +54,11 @@ jest.mock('../../hooks/use_get_available_rules_with_descriptions', () => ({ useGetAvailableRulesWithDescriptions: jest.fn(), })); -jest.mock('@kbn/observability-shared-plugin/public'); +jest.mock('@kbn/observability-shared-plugin/public', () => ({ + ...jest.requireActual('@kbn/observability-shared-plugin/public'), + useBreadcrumbs: jest.fn(), + TagsList: jest.fn(), +})); jest.mock('@kbn/response-ops-rule-form/src/rule_type_modal', () => ({ RuleTypeModal: ({ onSelectRuleType }: RuleTypeModalProps) => ( From 23d22e39c30fd94232910f27fa4dac73e1b8adef Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Mon, 21 Apr 2025 11:24:36 -0400 Subject: [PATCH 5/5] remove extra alert action --- .../alert_actions/alert_actions.tsx | 23 ------------------- 1 file changed, 23 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 2f251526a9821..cd0e2f9636388 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 @@ -20,7 +20,6 @@ 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 { ALERT_UUID } from '@kbn/rule-data-utils'; import { useKibana } from '../../utils/kibana_react'; import { useCaseActions } from './use_case_actions'; import { RULE_DETAILS_PAGE_ID } from '../../pages/rule_details/constants'; @@ -166,28 +165,10 @@ export const AlertActions: GetObservabilityAlertsTableProp<'renderActionsCell'> defaultMessage: 'More actions', }); - const onExpandEvent = () => { - const parsedAlert = parseAlert(observabilityRuleTypeRegistry)(alert); - openAlertInFlyout?.(parsedAlert.fields[ALERT_UUID]); - }; - const hideViewInApp = isInApp || viewInAppUrl === '' || parentAlert; return ( <> - {!parentAlert && ( - - - - - - )} {!hideViewInApp && ( // eslint-disable-next-line import/no-default-export export default AlertActions; -const VIEW_DETAILS = i18n.translate('xpack.observability.alertsTable.viewDetailsTextLabel', { - defaultMessage: 'Alert details', -}); - export type AlertActions = typeof AlertActions;