From 62c3aecffe9388a984b4432670ade0ff6f085b75 Mon Sep 17 00:00:00 2001 From: jennypavlova Date: Mon, 9 Dec 2024 12:48:15 +0100 Subject: [PATCH] [Inventory][Hosts] Make alert count consistent with the alerts page using a filter (#203003) Closes #202979 ## Summary This PR fixes inconsistencies between the alerts shown on hosts view and inventory and the alerts page. The fix includes using consistent rule types and filters for both getting the alert count and filters applied on the alert page ## How to Test Service Alert ( Inventory ) https://github.com/user-attachments/assets/f3b626da-1a49-42dc-a989-48b13d15ae2c Hosts view (+filters) Alerts (Inventory & Custom Threshold rule) https://github.com/user-attachments/assets/2a490ad4-e2a4-43b5-b00f-d00ac27f9fd3 Single Host https://github.com/user-attachments/assets/7c6a8cf7-f2a2-41f0-9f98-7e7543d4e7d5 cc: @roshan-elastic Ping to check the videos of the fix :) --- .../tabs/overview/alerts/alerts.tsx | 2 +- .../tabs/alerts/alerts_tab_content.tsx | 16 ++----- .../components/tabs/alerts_tab_badge.tsx | 4 +- .../alerts_badge/alerts_badge.test.tsx | 43 +++++++++++++++++-- .../components/alerts_badge/alerts_badge.tsx | 8 +++- 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/alerts/alerts.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/alerts/alerts.tsx index dc4d78baeb5a1..304e67a0debde 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/alerts/alerts.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/alerts/alerts.tsx @@ -75,7 +75,7 @@ export const AlertsSummaryContent = ({ )} diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx index 7b0ec7ed2d2f1..34726a68ab2dd 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx @@ -6,11 +6,7 @@ */ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { - AlertConsumers, - INFRA_RULE_TYPE_IDS, - OBSERVABILITY_RULE_TYPE_IDS, -} from '@kbn/rule-data-utils'; +import { AlertConsumers, OBSERVABILITY_RULE_TYPE_IDS } from '@kbn/rule-data-utils'; import { BrushEndListener, type XYBrushEvent } from '@elastic/charts'; import { useSummaryTimeRange } from '@kbn/observability-plugin/public'; import { useBoolean } from '@kbn/react-hooks'; @@ -47,11 +43,7 @@ export const AlertsTabContent = () => { const { alertsTableConfigurationRegistry, getAlertsStateTable: AlertsStateTable } = triggersActionsUi; - - const hostsWithAlertsKuery = hostNodes - .filter((host) => host.alertsCount) - .map((host) => `"${host.name}"`) - .join(' OR '); + const hostNamesKuery = hostNodes.map((host) => `host.name: "${host.name}"`).join(' OR '); return ( @@ -73,7 +65,7 @@ export const AlertsTabContent = () => { @@ -146,7 +138,7 @@ const MemoAlertSummaryWidget = React.memo( return ( { const { alertsEsQuery } = useAlertsQuery(); const { alertsCount, loading, error } = useAlertsCount({ - ruleTypeIds: INFRA_RULE_TYPE_IDS, + ruleTypeIds: OBSERVABILITY_RULE_TYPE_IDS, consumers: INFRA_ALERT_CONSUMERS, query: alertsEsQuery, }); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx index 1892dd0109490..0957fa4da8aea 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx @@ -15,10 +15,12 @@ const useKibanaMock = useKibana as jest.Mock; const commonEntityFields: Partial = { entityLastSeenTimestamp: 'foo', - entityId: 'entity1', + entityId: '1', }; describe('AlertsBadge', () => { + const mockAsKqlFilter = jest.fn(); + beforeEach(() => { jest.clearAllMocks(); @@ -29,6 +31,11 @@ describe('AlertsBadge', () => { prepend: (path: string) => path, }, }, + entityManager: { + entityClient: { + asKqlFilter: mockAsKqlFilter, + }, + }, }, }); }); @@ -52,10 +59,11 @@ describe('AlertsBadge', () => { provider: null, }, }; + mockAsKqlFilter.mockReturnValue('host.name: "foo"'); render(); expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.getAttribute('href')).toEqual( - `/app/observability/alerts?_a=(kuery:\"entity1\",status:active)` + `/app/observability/alerts?_a=(kuery:'host.name: "foo"',status:active)` ); expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('1'); }); @@ -78,11 +86,40 @@ describe('AlertsBadge', () => { alertsCount: 5, }; + mockAsKqlFilter.mockReturnValue('service.name: "bar"'); render(); expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.getAttribute('href')).toEqual( - `/app/observability/alerts?_a=(kuery:\"entity1\",status:active)` + `/app/observability/alerts?_a=(kuery:'service.name: "bar"',status:active)` ); expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('5'); }); + it('render alerts badge for a service entity with multiple identity fields', () => { + const entity: InventoryEntity = { + ...(commonEntityFields as InventoryEntity), + entityType: 'service', + entityDisplayName: 'foo', + entityIdentityFields: ['service.name', 'service.environment'], + entityDefinitionId: 'service', + service: { + name: 'bar', + environment: 'prod', + }, + agent: { + name: 'node', + }, + cloud: { + provider: null, + }, + alertsCount: 2, + }; + + mockAsKqlFilter.mockReturnValue('service.name: "bar" AND service.environment: "prod"'); + + render(); + expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.getAttribute('href')).toEqual( + `/app/observability/alerts?_a=(kuery:'service.name: "bar" AND service.environment: "prod"',status:active)` + ); + expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('2'); + }); }); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.tsx b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.tsx index 228cd3d8bbfd8..301dcb63d1a17 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.tsx @@ -15,12 +15,18 @@ export function AlertsBadge({ entity }: { entity: InventoryEntity }) { const { services: { http: { basePath }, + entityManager, }, } = useKibana(); const activeAlertsHref = basePath.prepend( `/app/observability/alerts?_a=${rison.encode({ - kuery: `"${entity.entityId}"`, + kuery: entityManager.entityClient.asKqlFilter({ + entity: { + identity_fields: entity.entityIdentityFields, + }, + ...entity, + }), status: 'active', })}` );