diff --git a/src/platform/packages/shared/kbn-discover-utils/src/field_constants.ts b/src/platform/packages/shared/kbn-discover-utils/src/field_constants.ts
index 9f9be93040361..f27a1a4cef670 100644
--- a/src/platform/packages/shared/kbn-discover-utils/src/field_constants.ts
+++ b/src/platform/packages/shared/kbn-discover-utils/src/field_constants.ts
@@ -16,6 +16,7 @@ export const ERROR_MESSAGE_FIELD = 'error.message';
export const EVENT_ORIGINAL_FIELD = 'event.original';
export const EVENT_OUTCOME_FIELD = 'event.outcome';
export const INDEX_FIELD = '_index';
+export const EVENT_CATEGORY_FIELD = 'event.category';
// Trace fields
export const TRACE_ID_FIELD = 'trace.id';
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx
index 864faa1813884..eb86d34e3e37c 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx
+++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.test.tsx
@@ -12,7 +12,6 @@ import { fireEvent, render, screen } from '@testing-library/react';
import { AlertEventOverview } from './alert_event_overview';
import type { DataTableRecord } from '@kbn/discover-utils';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
-import { EcsFlat } from '@elastic/ecs';
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
import { encode } from '@kbn/rison';
import { URLSearchParams } from 'url';
@@ -27,6 +26,19 @@ const mockDiscoverServices = {
application: {
getUrlForApp: mockGetUrlForApp,
},
+ fieldsMetadata: {
+ useFieldsMetadata: jest.fn().mockReturnValue({
+ fieldsMetadata: {
+ 'event.category': {
+ allowed_values: [
+ { name: 'process', description: 'Process events' },
+ { name: 'network', description: 'Network events' },
+ ],
+ },
+ },
+ loading: false,
+ }),
+ },
};
const mockRow = {
@@ -36,6 +48,7 @@ const mockRow = {
_id: 'test-id',
'@timestamp': '2021-08-02T14:00:00.000Z',
'kibana.alert.url': 'test-url',
+ 'event.category': 'process',
};
const mockHit = {
@@ -87,10 +100,7 @@ describe('AlertEventOverview', () => {
render();
- expect(screen.getByTestId('expandableContent-About')).toHaveTextContent(
- EcsFlat['event.category'].allowed_values.find((i) => i.name === 'process')
- ?.description as string
- );
+ expect(screen.getByTestId('expandableContent-About')).toHaveTextContent('Process events');
});
test('should display timeline redirect url correctly', () => {
@@ -142,5 +152,73 @@ describe('AlertEventOverview', () => {
`test-timeline-url?${searchParams}`
);
});
+
+ describe('ECS Description', () => {
+ test('should give ECS description for event.category field', () => {
+ render();
+ expect(screen.getByTestId('about')).toHaveTextContent('Process events');
+ });
+ test('should give placeholder ECS description when fieldsMetadata is not available', () => {
+ (useDiscoverServices as jest.Mock).mockReturnValue({
+ ...mockDiscoverServices,
+ fieldsMetadata: {
+ useFieldsMetadata: jest.fn().mockReturnValue({
+ fieldsMetadata: undefined,
+ loading: false,
+ }),
+ },
+ });
+
+ render();
+ expect(screen.getByTestId('about')).toHaveTextContent(
+ "This field doesn't have a description because it's not part of ECS."
+ );
+ });
+
+ test('should give placeholder ECS description when event.category field is not present in fieldMetada', () => {
+ (useDiscoverServices as jest.Mock).mockReturnValue({
+ ...mockDiscoverServices,
+ fieldsMetadata: {
+ useFieldsMetadata: jest.fn().mockReturnValue({
+ fieldsMetadata: {},
+ loading: false,
+ }),
+ },
+ });
+
+ render();
+ expect(screen.getByTestId('about')).toHaveTextContent(
+ "This field doesn't have a description because it's not part of ECS."
+ );
+ });
+
+ test('should give placeholder ECS description when event.category field is not present in hit', () => {
+ const localMockHit = {
+ flattened: {
+ ...mockRow,
+ 'event.category': undefined,
+ },
+ } as unknown as DataTableRecord;
+
+ render();
+ expect(screen.getByTestId('about')).toHaveTextContent(
+ "This field doesn't have a description because it's not part of ECS."
+ );
+ });
+
+ test('should give placeholder ECS when event.category fields has a value that is not in allowed values', () => {
+ const localMockHit = {
+ flattened: {
+ ...mockRow,
+ 'event.category': 'unknown',
+ },
+ } as unknown as DataTableRecord;
+
+ render();
+ expect(screen.getByTestId('about')).toHaveTextContent(
+ "This field doesn't have a description because it's not part of ECS."
+ );
+ });
+ });
});
});
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx
index c33210d17b7c0..4bb98c9f51974 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx
+++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/components/alert_event_overview.tsx
@@ -9,7 +9,7 @@
import React, { useMemo, useState } from 'react';
import type { FC, PropsWithChildren } from 'react';
-import { getFieldValue } from '@kbn/discover-utils';
+import { fieldConstants, getFieldValue } from '@kbn/discover-utils';
import type { DocViewerComponent } from '@kbn/unified-doc-viewer/src/services/types';
import {
EuiTitle,
@@ -19,6 +19,7 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiText,
+ EuiSkeletonText,
} from '@elastic/eui';
import * as i18n from '../translations';
import { getSecurityTimelineRedirectUrl } from '../utils';
@@ -62,12 +63,18 @@ export const ExpandableSection: FC> = ({
export const AlertEventOverview: DocViewerComponent = ({ hit }) => {
const {
application: { getUrlForApp },
+ fieldsMetadata,
} = useDiscoverServices();
const timelinesURL = getUrlForApp('securitySolutionUI', {
path: 'alerts',
});
+ const result = fieldsMetadata?.useFieldsMetadata({
+ attributes: ['allowed_values', 'name', 'flat_name'],
+ fieldNames: [fieldConstants.EVENT_CATEGORY_FIELD],
+ });
+
const reason = useMemo(() => getFieldValue(hit, 'kibana.alert.reason') as string, [hit]);
const description = useMemo(
() => getFieldValue(hit, 'kibana.alert.rule.description') as string,
@@ -100,9 +107,18 @@ export const AlertEventOverview: DocViewerComponent = ({ hit }) => {
>
-
- {getEcsAllowedValueDescription(eventCategory)}
-
+ {result?.loading ? (
+
+ ) : (
+
+ {getEcsAllowedValueDescription(result?.fieldsMetadata, eventCategory)}
+
+ )}
{description ? (
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts
index b20afd9b01eb9..fb2fd398fe333 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts
+++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/translations.ts
@@ -54,3 +54,10 @@ export const reasonSectionTitle = i18n.translate(
defaultMessage: 'Reason',
}
);
+
+export const ecsDescriptionLoadingAriaLable = i18n.translate(
+ 'discover.profile.security.flyout.ecsDescriptionLoadingAriaLabel',
+ {
+ defaultMessage: 'Loading ECS description',
+ }
+);
diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/ecs_description.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/ecs_description.ts
index a26a787b324de..ca4dd264c97a0 100644
--- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/ecs_description.ts
+++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/utils/ecs_description.ts
@@ -7,9 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { EcsFlat } from '@elastic/ecs';
+import type { UseFieldsMetadataHook } from '@kbn/fields-metadata-plugin/public/hooks/use_fields_metadata';
import * as i18n from '../translations';
-export type EcsAllowedValue = (typeof EcsFlat)['event.category']['allowed_values'][0];
/**
* Helper function to return the description of an allowed value of the specified field
@@ -17,8 +16,11 @@ export type EcsAllowedValue = (typeof EcsFlat)['event.category']['allowed_values
* @param value
* @returns ecs description of the value
*/
-export const getEcsAllowedValueDescription = (value: string): string => {
- const allowedValues: EcsAllowedValue[] = EcsFlat['event.category']?.allowed_values ?? [];
+export const getEcsAllowedValueDescription = (
+ fieldsMetadata: ReturnType['fieldsMetadata'] = {},
+ value: string
+): string => {
+ const allowedValues = fieldsMetadata['event.category']?.allowed_values ?? [];
const result =
allowedValues?.find((item) => item.name === value)?.description ?? i18n.noEcsDescriptionReason;
return result;