Skip to content

Commit

Permalink
[Infra] Show all alerts in Infra views (Host + Containers) for consis…
Browse files Browse the repository at this point in the history
…tency with entity inventory (elastic#202188)

## Summary

Closes elastic#201567
Fixes elastic#181580 and elastic#201528

This PR ensures that Infra views display all alerts, regardless of index
pattern.
It provides consistent alert visibility across services.
- **Alert counts per host in the Hosts List now shows all host alerts**
- **Alert counts in alerting tab in Hosts List now shows all host
alerts**
- **Hosts Views now shows all host alerts**
- **Container Views now shows all container alerts**
- **Alert counts shown in Hosts Lists and Host Views now are consistent
with alerting app when filtered by host**
- **Clicking through alerts in host/container views now filters by just
the entity ID**
- **Clicking `Show all` in `Hosts` now goes to Alerts app with all hosts
that have alerts in the filter**
- **Clicking an Alert in Entity Inventory now searches by just the
entity ID**

## Screenshots
Screen|Before|After
-|-|-
Entity inventory (no changes here, just showing the alert count for next
screens)|<img width="1276" alt="image"
src="https://github.com/user-attachments/assets/e6054957-b9d8-4d19-8ce3-b101f60099a2">|<img
width="772" alt="image"
src="https://github.com/user-attachments/assets/2b64fb4d-ff60-4c6b-8e15-3ccb5ae6621b">
Host detail view|<img width="662" alt="image"
src="https://github.com/user-attachments/assets/0dd405bc-888a-4fd0-9412-6016e8ae3795">|<img
width="775" alt="image"
src="https://github.com/user-attachments/assets/bf694b1d-b7bb-4cc8-abd1-bebb2070d11c">
Host List view|<img width="536" alt="image"
src="https://github.com/user-attachments/assets/a9c7ae89-8549-49d0-aeb3-3dc28dd3f713"><img
width="894" alt="image"
src="https://github.com/user-attachments/assets/38433bfb-66b0-4c08-824a-ccf46ad93e50">|<img
width="582" alt="image"
src="https://github.com/user-attachments/assets/7addf149-9f12-45a3-b443-9cd069edc4a9"><img
width="731" alt="image"
src="https://github.com/user-attachments/assets/51e80e6c-504b-40b0-80b7-a9a58a6ad153">
Container detail view|<img width="676" alt="image"
src="https://github.com/user-attachments/assets/851cafec-d580-4184-a7a1-7b87620ea1b4">|<img
width="698" alt="image"
src="https://github.com/user-attachments/assets/aebdd4f5-26f1-4b74-8cf4-37dc74aad112">
Click `Show all` in Host List|<img width="696" alt="image"
src="https://github.com/user-attachments/assets/0557f9f8-aee3-4898-be17-372f3522f160">|<img
width="1096" alt="image"
src="https://github.com/user-attachments/assets/c5ace835-a56b-45cb-95c7-fa978d7840f5">
Click `Show all` in Host/Container detail view|<img width="712"
alt="image"
src="https://github.com/user-attachments/assets/eb04c059-aff0-48f4-90fb-73c08943cb57">|<img
width="748" alt="image"
src="https://github.com/user-attachments/assets/189f686d-5931-4f42-bf14-ac9f1ae987e4">
Click alert in Entity Inventory|<img width="677" alt="image"
src="https://github.com/user-attachments/assets/e99f70a1-c217-4225-acf4-25285fff8282">|<img
width="637" alt="image"
src="https://github.com/user-attachments/assets/9aa77aa4-7a77-45ac-8cf4-95b5203d0f5a">

---------

Co-authored-by: Cauê Marcondes <[email protected]>
  • Loading branch information
2 people authored and CAWilson94 committed Dec 12, 2024
1 parent 3f998f9 commit cf2589c
Show file tree
Hide file tree
Showing 9 changed files with 35 additions and 65 deletions.
16 changes: 13 additions & 3 deletions x-pack/plugins/observability_solution/infra/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,24 @@
* 2.0.
*/

import { AlertConsumers, ValidFeatureId } from '@kbn/rule-data-utils';

export const METRICS_INDEX_PATTERN = 'metrics-*,metricbeat-*';
export const LOGS_INDEX_PATTERN = 'logs-*,filebeat-*,kibana_sample_data_logs*';
export const METRICS_APP = 'metrics';
export const LOGS_APP = 'logs';

export const METRICS_FEATURE_ID = 'infrastructure';
export const INFRA_ALERT_FEATURE_ID = 'infrastructure';
export const LOGS_FEATURE_ID = 'logs';
export const METRICS_FEATURE_ID = AlertConsumers.INFRASTRUCTURE;
export const INFRA_ALERT_FEATURE_ID = AlertConsumers.INFRASTRUCTURE;
export const LOGS_FEATURE_ID = AlertConsumers.LOGS;

export const INFRA_ALERT_FEATURE_IDS: ValidFeatureId[] = [
AlertConsumers.INFRASTRUCTURE,
AlertConsumers.OBSERVABILITY,
AlertConsumers.LOGS,
AlertConsumers.SLO,
AlertConsumers.APM,
];

export type InfraFeatureId = typeof METRICS_FEATURE_ID | typeof LOGS_FEATURE_ID;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const AlertsSummaryContent = ({
)}
<EuiFlexItem grow={false}>
<LinkToAlertsPage
kuery={`${assetIdField}:"${assetId}"`}
kuery={`"${assetId}"`}
dateRange={dateRange}
data-test-subj="infraAssetDetailsAlertsTabAlertsShowAllButton"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ interface AlertsOverviewProps {
assetType?: InventoryItemType;
}

const alertFeatureIds = [...infraAlertFeatureIds, AlertConsumers.OBSERVABILITY];

export const AlertsOverview = ({
assetId,
dateRange,
Expand Down Expand Up @@ -127,7 +125,7 @@ export const AlertsOverview = ({
alertsTableConfigurationRegistry={alertsTableConfigurationRegistry}
id={'assetDetailsAlertsTable'}
configurationId={AlertConsumers.OBSERVABILITY}
featureIds={alertFeatureIds}
featureIds={infraAlertFeatureIds}
showAlertStatusWithFlapping
query={alertsEsQueryByStatus}
initialPageSize={5}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import {
ALERT_STATUS_ACTIVE,
ALERT_STATUS_RECOVERED,
ALERT_STATUS_UNTRACKED,
AlertConsumers,
} from '@kbn/rule-data-utils';
import type { Filter } from '@kbn/es-query';
import type { AlertStatus } from '@kbn/observability-plugin/common/typings';
import type { ValidFeatureId } from '@kbn/rule-data-utils';
import { INFRA_ALERT_FEATURE_IDS } from '../../../../common/constants';

export const ALERT_STATUS_ALL = 'all';

Expand Down Expand Up @@ -88,5 +87,4 @@ export const ALERTS_PATH = '/app/observability/alerts';
export const ALERTS_PER_PAGE = 10;
export const ALERTS_TABLE_ID = 'xpack.infra.hosts.alerts.table';

export const INFRA_ALERT_FEATURE_ID = 'infrastructure';
export const infraAlertFeatureIds: ValidFeatureId[] = [AlertConsumers.INFRASTRUCTURE];
export const infraAlertFeatureIds = INFRA_ALERT_FEATURE_IDS;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { AlertConsumers, ALERT_RULE_PRODUCER } from '@kbn/rule-data-utils';
import { AlertConsumers } 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';
Expand All @@ -25,13 +25,14 @@ import {
import AlertsStatusFilter from '../../../../../../components/shared/alerts/alerts_status_filter';
import { CreateAlertRuleButton } from '../../../../../../components/shared/alerts/links/create_alert_rule_button';
import { LinkToAlertsPage } from '../../../../../../components/shared/alerts/links/link_to_alerts_page';
import { INFRA_ALERT_FEATURE_ID } from '../../../../../../../common/constants';
import { AlertFlyout } from '../../../../../../alerting/inventory/components/alert_flyout';
import { usePluginConfig } from '../../../../../../containers/plugin_config_context';
import { useHostsViewContext } from '../../../hooks/use_hosts_view';

export const AlertsTabContent = () => {
const { services } = useKibanaContextForPlugin();
const { featureFlags } = usePluginConfig();
const { hostNodes } = useHostsViewContext();

const { alertStatus, setAlertStatus, alertsEsQueryByStatus } = useAlertsQuery();
const [isAlertFlyoutVisible, { toggle: toggleAlertFlyout }] = useBoolean(false);
Expand All @@ -43,6 +44,11 @@ export const AlertsTabContent = () => {
const { alertsTableConfigurationRegistry, getAlertsStateTable: AlertsStateTable } =
triggersActionsUi;

const hostsWithAlertsKuery = hostNodes
.filter((host) => host.alertsCount)
.map((host) => `"${host.name}"`)
.join(' OR ');

return (
<HeightRetainer>
<EuiFlexGroup direction="column" gutterSize="m" data-test-subj="hostsView-alerts">
Expand All @@ -63,7 +69,7 @@ export const AlertsTabContent = () => {
<LinkToAlertsPage
dateRange={searchCriteria.dateRange}
data-test-subj="infraHostAlertsTabAlertsShowAllButton"
kuery={`${ALERT_RULE_PRODUCER}: ${INFRA_ALERT_FEATURE_ID}`}
kuery={`${hostsWithAlertsKuery}`}
/>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { isEmpty } from 'lodash';
import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types';
import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
import type { KibanaRequest } from '@kbn/core/server';
import { INFRA_ALERT_FEATURE_IDS } from '../../../common/constants';
import type { InfraBackendLibs } from '../infra_types';

type RequiredParams = ESSearchRequest & {
Expand All @@ -26,7 +27,7 @@ export async function getInfraAlertsClient({
}) {
const [, { ruleRegistry }] = await libs.getStartServices();
const alertsClient = await ruleRegistry.getRacClientWithRequest(request);
const infraAlertsIndices = await alertsClient.getAuthorizedAlertsIndices(['infrastructure']);
const infraAlertsIndices = await alertsClient.getAuthorizedAlertsIndices(INFRA_ALERT_FEATURE_IDS);

if (!infraAlertsIndices || isEmpty(infraAlertsIndices)) {
throw Error('No alert indices exist for "infrastructure"');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@
*/

import { termQuery, termsQuery } from '@kbn/observability-plugin/server';
import { observabilityFeatureId } from '@kbn/observability-shared-plugin/common';
import {
ALERT_RULE_PRODUCER,
ALERT_STATUS,
ALERT_STATUS_ACTIVE,
ALERT_UUID,
} from '@kbn/rule-data-utils';
import { HOST_NAME_FIELD, INFRA_ALERT_FEATURE_ID } from '../../../../../common/constants';
import { HOST_NAME_FIELD, INFRA_ALERT_FEATURE_IDS } from '../../../../../common/constants';
import { GetHostParameters } from '../types';

export async function getHostsAlertsCount({
Expand Down Expand Up @@ -41,7 +40,7 @@ export async function getHostsAlertsCount({
query: {
bool: {
filter: [
...termsQuery(ALERT_RULE_PRODUCER, INFRA_ALERT_FEATURE_ID, observabilityFeatureId),
...termsQuery(ALERT_RULE_PRODUCER, ...INFRA_ALERT_FEATURE_IDS),
...termQuery(ALERT_STATUS, ALERT_STATUS_ACTIVE),
...termsQuery(HOST_NAME_FIELD, ...hostNames),
...rangeQuery,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ const useKibanaMock = useKibana as jest.Mock;

const commonEntityFields: Partial<InventoryEntity> = {
entityLastSeenTimestamp: 'foo',
entityId: '1',
entityId: 'entity1',
};

describe('AlertsBadge', () => {
const mockAsKqlFilter = jest.fn();

beforeEach(() => {
jest.clearAllMocks();

Expand All @@ -31,11 +29,6 @@ describe('AlertsBadge', () => {
prepend: (path: string) => path,
},
},
entityManager: {
entityClient: {
asKqlFilter: mockAsKqlFilter,
},
},
},
});
});
Expand All @@ -59,11 +52,10 @@ describe('AlertsBadge', () => {
provider: null,
},
};
mockAsKqlFilter.mockReturnValue('host.name: foo');

render(<AlertsBadge entity={entity} />);
expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.getAttribute('href')).toEqual(
"/app/observability/alerts?_a=(kuery:'host.name: foo',status:active)"
`/app/observability/alerts?_a=(kuery:\"entity1\",status:active)`
);
expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('1');
});
Expand All @@ -86,40 +78,11 @@ describe('AlertsBadge', () => {

alertsCount: 5,
};
mockAsKqlFilter.mockReturnValue('service.name: bar');

render(<AlertsBadge entity={entity} />);
expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.getAttribute('href')).toEqual(
"/app/observability/alerts?_a=(kuery:'service.name: bar',status:active)"
`/app/observability/alerts?_a=(kuery:\"entity1\",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(<AlertsBadge entity={entity} />);
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');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,16 @@ export function AlertsBadge({ entity }: { entity: InventoryEntity }) {
const {
services: {
http: { basePath },
entityManager,
},
} = useKibana();

const activeAlertsHref = basePath.prepend(
`/app/observability/alerts?_a=${rison.encode({
kuery: entityManager.entityClient.asKqlFilter({
entity: {
identity_fields: entity.entityIdentityFields,
},
...entity,
}),
kuery: `"${entity.entityId}"`,
status: 'active',
})}`
);

return (
<EuiToolTip
position="bottom"
Expand Down

0 comments on commit cf2589c

Please sign in to comment.