Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ const AlertsTableContent = typedForwardRef(
alerts: { sync: casesConfiguration?.syncAlerts ?? false },
observables: {
enabled: true,
autoExtract: false,
autoExtract: casesConfiguration?.extractObservables ?? false,
},
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import { EuiPopover, EuiButtonEmpty, EuiContextMenu } from '@elastic/eui';
import numeral from '@elastic/numeral';
import {
ALERT_CASE_IDS,
ALERT_RULE_NAME,
ALERT_RULE_UUID,
ALERT_WORKFLOW_ASSIGNEE_IDS,
ALERT_WORKFLOW_TAGS,
} from '@kbn/rule-data-utils';
Expand Down Expand Up @@ -44,19 +42,24 @@ const selectedIdsToTimelineItemMapper = (
): TimelineItem[] => {
return Array.from(rowSelection.keys()).map((rowIndex: number) => {
const alert = alerts[rowIndex];
const data = Object.entries(alert).map(([key, value]) => ({
Copy link
Member

@umbopepato umbopepato Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: how about adding the fallback values for the well known fields here instead of iterating on the fields array each time?

Suggested change
const data = Object.entries(alert).map(([key, value]) => ({
const data = Object.entries({
[ALERT_CASE_IDS]: [],
[ALERT_WORKFLOW_TAGS]: [],
[ALERT_WORKFLOW_ASSIGNEE_IDS]: [],
...alert,
}).map(([key, value]) => ({

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, missed this comment before I merged. Made a tiny pr to fix here: #237307

field: key,
value: value ? (value as string[]) : [],
}));
if (!data.some((item) => item.field === ALERT_CASE_IDS)) {
data.push({ field: ALERT_CASE_IDS, value: [] });
}
if (!data.some((item) => item.field === ALERT_WORKFLOW_TAGS)) {
data.push({ field: ALERT_WORKFLOW_TAGS, value: [] });
}
if (!data.some((item) => item.field === ALERT_WORKFLOW_ASSIGNEE_IDS)) {
data.push({ field: ALERT_WORKFLOW_ASSIGNEE_IDS, value: [] });
}

return {
_id: alert._id,
_index: alert._index,
data: [
{ field: ALERT_RULE_NAME, value: alert[ALERT_RULE_NAME] as string[] },
{ field: ALERT_RULE_UUID, value: alert[ALERT_RULE_UUID] as string[] },
{ field: ALERT_CASE_IDS, value: (alert[ALERT_CASE_IDS] ?? []) as string[] },
{ field: ALERT_WORKFLOW_TAGS, value: (alert[ALERT_WORKFLOW_TAGS] ?? []) as string[] },
{
field: ALERT_WORKFLOW_ASSIGNEE_IDS,
value: (alert[ALERT_WORKFLOW_ASSIGNEE_IDS] ?? []) as string[],
},
],
data,
ecs: {
_id: alert._id,
_index: alert._index,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ export const useBulkAddToCaseActions = ({
const caseAttachments = alerts
? casesService?.helpers.groupAlertsByRule(alerts) ?? []
: [];

const dataArray = alerts ? alerts.map((alert) => alert.data) : [];
const observables = casesService?.helpers.getObservablesFromEcs(dataArray);
createCaseFlyout.open({
attachments: caseAttachments,
observables,
});
},
},
Expand All @@ -179,6 +181,11 @@ export const useBulkAddToCaseActions = ({
groupAlertsByRule: casesService?.helpers.groupAlertsByRule,
});
},
getObservables: ({ theCase }) => {
if (!alerts || theCase == null) return [];
const dataArray = alerts.map((alert) => alert.data);
return casesService?.helpers.getObservablesFromEcs(dataArray) ?? [];
},
});
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const helpersMock: jest.MockedObject<CasesService['helpers']> = {
canUseCases: jest.fn(),
groupAlertsByRule: jest.fn(),
getRuleIdFromEvent: jest.fn(),
getObservablesFromEcs: jest.fn(),
};

export const createCasesServiceMock = (): jest.MaybeMockedDeep<CasesService> => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,22 @@ describe('AlertsDataGrid bulk actions', () => {
_id: 'alert0',
_index: 'idx0',
data: [
{
field: '_id',
value: 'alert0',
},
{
field: '_index',
value: 'idx0',
},
{
field: 'kibana.alert.rule.name',
value: ['one'],
},
{
field: 'kibana.alert.reason',
value: ['two'],
},
{
field: 'kibana.alert.rule.uuid',
value: ['uuidone'],
Expand Down Expand Up @@ -526,10 +538,16 @@ describe('AlertsDataGrid bulk actions', () => {
_id: 'alert1',
_index: 'idx1',
data: [
{ field: '_id', value: 'alert1' },
{ field: '_index', value: 'idx1' },
{
field: 'kibana.alert.rule.name',
value: ['three'],
},
{
field: 'kibana.alert.reason',
value: ['four'],
},
{
field: 'kibana.alert.rule.uuid',
value: ['uuidtwo'],
Expand Down Expand Up @@ -740,10 +758,22 @@ describe('AlertsDataGrid bulk actions', () => {
_id: 'alert0',
_index: 'idx0',
data: [
{
field: '_id',
value: 'alert0',
},
{
field: '_index',
value: 'idx0',
},
{
field: 'kibana.alert.rule.name',
value: ['one'],
},
{
field: 'kibana.alert.reason',
value: ['two'],
},
{
field: 'kibana.alert.rule.uuid',
value: ['uuidone'],
Expand All @@ -770,10 +800,16 @@ describe('AlertsDataGrid bulk actions', () => {
_id: 'alert1',
_index: 'idx1',
data: [
{ field: '_id', value: 'alert1' },
{ field: '_index', value: 'idx1' },
{
field: 'kibana.alert.rule.name',
value: ['three'],
},
{
field: 'kibana.alert.reason',
value: ['four'],
},
{
field: 'kibana.alert.rule.uuid',
value: ['uuidtwo'],
Expand Down
11 changes: 10 additions & 1 deletion src/platform/packages/shared/response-ops/alerts-table/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export interface Consumer {
name: string;
}

interface Observable {
typeKey: string;
value: string;
description: string | null;
}

export type AlertsTableSupportedConsumers = Exclude<AlertConsumers, 'alerts' | 'streams'>;

export type CellComponent = NonNullable<AlertsTableProps['renderCellValue']>;
Expand All @@ -78,7 +84,7 @@ export interface SystemCellComponentMap {
export type SystemCellId = keyof SystemCellComponentMap;

type UseCasesAddToNewCaseFlyout = (props?: Record<string, unknown> & { onSuccess: () => void }) => {
open: ({ attachments }: { attachments: any[] }) => void;
open: ({ attachments, observables }: { attachments: any[]; observables?: any[] }) => void;
close: () => void;
};

Expand All @@ -87,8 +93,10 @@ type UseCasesAddToExistingCaseModal = (
) => {
open: ({
getAttachments,
getObservables,
}: {
getAttachments: ({ theCase }: { theCase?: { id: string } }) => any[];
getObservables?: ({ theCase }: { theCase?: { id: string } }) => any[];
}) => void;
close: () => void;
};
Expand Down Expand Up @@ -121,6 +129,7 @@ export interface CasesService {
groupAlertsByRule: (items: any[]) => any[];
canUseCases: (owners: Array<'securitySolution' | 'observability' | 'cases'>) => any;
getRuleIdFromEvent: (event: { data: any[]; ecs: Ecs }) => { id: string; name: string };
getObservablesFromEcs: (ecsArray: any[][]) => Observable[];
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@
* 2.0.
*/
import type { ObservablePost } from '../../../common/types/api';
import {
getIPType,
getHashValues,
getObservablesFromEcs,
processObservable,
} from './get_observables_from_ecs';
import { getIPType, getObservablesFromEcs, processObservable } from './get_observables_from_ecs';

describe('getIPType', () => {
it('should return IPV4 for a valid IPv4 address', () => {
Expand Down Expand Up @@ -65,53 +60,18 @@ describe('processObservable', () => {
});
});

describe('getHashValues', () => {
it('should return an empty array for a valid Ecs data', () => {
expect(getHashValues({ _id: '1', _index: '2' })).toEqual([]);
});
it('should return an array of hash values for a valid Ecs data', () => {
expect(
getHashValues({
_id: '1',
_index: '2',
file: {
hash: {
md5: ['md5hasg'],
sha256: ['sha1hash'],
},
},
process: {
hash: {
md5: ['ssdeephash'],
},
},
})
).toEqual(['md5hasg', 'sha1hash', 'ssdeephash']);
});
});

describe('getObservablesFromEcsData', () => {
describe('getObservablesFromEcsDataArray', () => {
it('should return an array of observables for a valid Ecs data', () => {
expect(
getObservablesFromEcs({
_id: '1',
_index: '2',
source: { ip: ['192.168.1.1'] },
destination: { ip: ['023:023:023:023:023:023:023:023'] },
host: {
name: ['host1'],
},
file: {
hash: {
sha256: ['sha256hash', 'sha256hash2'],
},
},
dns: {
question: {
name: ['example.com', 'example.org'],
},
},
})
getObservablesFromEcs([
[
{ field: 'source.ip', value: ['192.168.1.1'] },
{ field: 'destination.ip', value: ['023:023:023:023:023:023:023:023'] },
{ field: 'host.name', value: ['host1'] },
{ field: 'file.hash.sha256', value: ['sha256hash', 'sha256hash2'] },
{ field: 'dns.question.name', value: ['example.com', 'example.org'] },
],
])
).toEqual([
{
typeKey: 'observable-type-ipv4',
Expand Down Expand Up @@ -153,16 +113,25 @@ describe('getObservablesFromEcsData', () => {

it('should return unique observables', () => {
expect(
getObservablesFromEcs({
_id: '1',
_index: '2',
file: {
hash: {
sha512: ['sha'],
sha256: ['sha'],
},
},
})
getObservablesFromEcs([
[
{ field: 'file.hash.sha512', value: ['sha'] },
{ field: 'file.hash.sha256', value: ['sha'] },
],
])
).toEqual([
{
typeKey: 'observable-type-file-hash',
value: 'sha',
description: 'Auto extracted observable',
},
]);
});
it('should not include observables with no value', () => {
expect(
getObservablesFromEcs([
[{ field: 'host.name' }, { field: 'file.hash.sha512', value: ['sha'] }],
])
).toEqual([
{
typeKey: 'observable-type-file-hash',
Expand All @@ -174,16 +143,12 @@ describe('getObservablesFromEcsData', () => {

it('should return observables with different key value pairs', () => {
expect(
getObservablesFromEcs({
_id: '1',
_index: '2',
host: {
name: ['name'],
},
file: {
path: ['name'],
},
})
getObservablesFromEcs([
[
{ field: 'host.name', value: ['name'] },
{ field: 'file.path', value: ['name'] },
],
])
).toEqual([
{
typeKey: 'observable-type-hostname',
Expand All @@ -197,4 +162,40 @@ describe('getObservablesFromEcsData', () => {
},
]);
});

it('should return correct observables from multiple ecs data arrays', () => {
expect(
getObservablesFromEcs([
[
{ field: 'host.name', value: ['host1'] },
{ field: 'file.path', value: ['path1'] },
],
[
{ field: 'host.name', value: ['host2'] },
{ field: 'file.path', value: ['path2'] },
],
])
).toEqual([
{
typeKey: 'observable-type-hostname',
value: 'host1',
description: 'Auto extracted observable',
},
{
typeKey: 'observable-type-file-path',
value: 'path1',
description: 'Auto extracted observable',
},
{
typeKey: 'observable-type-hostname',
value: 'host2',
description: 'Auto extracted observable',
},
{
typeKey: 'observable-type-file-path',
value: 'path2',
description: 'Auto extracted observable',
},
]);
});
});
Loading