Skip to content

Commit

Permalink
Reapply "[Security Solution] [Attack discovery] Output chunking / ref…
Browse files Browse the repository at this point in the history
…inement, LangGraph migration, and evaluation improvements (#195669)" (#196440)

#195669 + #196381

This reverts commit dbe6d82.

---------

Co-authored-by: Alex Szabo <[email protected]>
  • Loading branch information
jbudz and delanni authored Oct 16, 2024
1 parent 5ae7a61 commit ad2ac71
Show file tree
Hide file tree
Showing 190 changed files with 8,378 additions and 2,148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { getOpenAndAcknowledgedAlertsQuery } from './get_open_and_acknowledged_alerts_query';
import { getOpenAndAcknowledgedAlertsQuery } from '.';

describe('getOpenAndAcknowledgedAlertsQuery', () => {
it('returns the expected query', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
* 2.0.
*/

import type { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen';
import type { AnonymizationFieldResponse } from '../../schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen';

/**
* This query returns open and acknowledged (non-building block) alerts in the last 24 hours.
*
* The alerts are ordered by risk score, and then from the most recent to the oldest.
*/
export const getOpenAndAcknowledgedAlertsQuery = ({
alertsIndexPattern,
anonymizationFields,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { getRawDataOrDefault } from '.';

describe('getRawDataOrDefault', () => {
it('returns the raw data when it is valid', () => {
const rawData = {
field1: [1, 2, 3],
field2: ['a', 'b', 'c'],
};

expect(getRawDataOrDefault(rawData)).toEqual(rawData);
});

it('returns an empty object when the raw data is invalid', () => {
const rawData = {
field1: [1, 2, 3],
field2: 'invalid',
};

expect(getRawDataOrDefault(rawData)).toEqual({});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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 { isRawDataValid } from '../is_raw_data_valid';
import type { MaybeRawData } from '../types';

/** Returns the raw data if it valid, or a default if it's not */
export const getRawDataOrDefault = (rawData: MaybeRawData): Record<string, unknown[]> =>
isRawDataValid(rawData) ? rawData : {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 { isRawDataValid } from '.';

describe('isRawDataValid', () => {
it('returns true for valid raw data', () => {
const rawData = {
field1: [1, 2, 3], // the Fields API may return a number array
field2: ['a', 'b', 'c'], // the Fields API may return a string array
};

expect(isRawDataValid(rawData)).toBe(true);
});

it('returns true when a field array is empty', () => {
const rawData = {
field1: [1, 2, 3], // the Fields API may return a number array
field2: ['a', 'b', 'c'], // the Fields API may return a string array
field3: [], // the Fields API may return an empty array
};

expect(isRawDataValid(rawData)).toBe(true);
});

it('returns false when a field does not have an array of values', () => {
const rawData = {
field1: [1, 2, 3],
field2: 'invalid',
};

expect(isRawDataValid(rawData)).toBe(false);
});

it('returns true for empty raw data', () => {
const rawData = {};

expect(isRawDataValid(rawData)).toBe(true);
});

it('returns false when raw data is an unexpected type', () => {
const rawData = 1234;

// @ts-expect-error
expect(isRawDataValid(rawData)).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { MaybeRawData } from '../types';

export const isRawDataValid = (rawData: MaybeRawData): rawData is Record<string, unknown[]> =>
typeof rawData === 'object' && Object.keys(rawData).every((x) => Array.isArray(rawData[x]));
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 { sizeIsOutOfRange } from '.';
import { MAX_SIZE, MIN_SIZE } from '../types';

describe('sizeIsOutOfRange', () => {
it('returns true when size is undefined', () => {
const size = undefined;

expect(sizeIsOutOfRange(size)).toBe(true);
});

it('returns true when size is less than MIN_SIZE', () => {
const size = MIN_SIZE - 1;

expect(sizeIsOutOfRange(size)).toBe(true);
});

it('returns true when size is greater than MAX_SIZE', () => {
const size = MAX_SIZE + 1;

expect(sizeIsOutOfRange(size)).toBe(true);
});

it('returns false when size is exactly MIN_SIZE', () => {
const size = MIN_SIZE;

expect(sizeIsOutOfRange(size)).toBe(false);
});

it('returns false when size is exactly MAX_SIZE', () => {
const size = MAX_SIZE;

expect(sizeIsOutOfRange(size)).toBe(false);
});

it('returns false when size is within the valid range', () => {
const size = MIN_SIZE + 1;

expect(sizeIsOutOfRange(size)).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 { MAX_SIZE, MIN_SIZE } from '../types';

/** Return true if the provided size is out of range */
export const sizeIsOutOfRange = (size?: number): boolean =>
size == null || size < MIN_SIZE || size > MAX_SIZE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { SearchResponse } from '@elastic/elasticsearch/lib/api/types';

export const MIN_SIZE = 10;
export const MAX_SIZE = 10000;

/** currently the same shape as "fields" property in the ES response */
export type MaybeRawData = SearchResponse['fields'] | undefined;
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const AttackDiscovery = z.object({
/**
* A short (no more than a sentence) summary of the attack discovery featuring only the host.name and user.name fields (when they are applicable), using the same syntax
*/
entitySummaryMarkdown: z.string(),
entitySummaryMarkdown: z.string().optional(),
/**
* An array of MITRE ATT&CK tactic for the attack discovery
*/
Expand All @@ -55,7 +55,7 @@ export const AttackDiscovery = z.object({
/**
* The time the attack discovery was generated
*/
timestamp: NonEmptyString,
timestamp: NonEmptyString.optional(),
});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ components:
required:
- 'alertIds'
- 'detailsMarkdown'
- 'entitySummaryMarkdown'
- 'summaryMarkdown'
- 'timestamp'
- 'title'
properties:
alertIds:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ export type PostEvaluateBody = z.infer<typeof PostEvaluateBody>;
export const PostEvaluateBody = z.object({
graphs: z.array(z.string()),
datasetName: z.string(),
evaluatorConnectorId: z.string().optional(),
connectorIds: z.array(z.string()),
runName: z.string().optional(),
alertsIndexPattern: z.string().optional().default('.alerts-security.alerts-default'),
langSmithApiKey: z.string().optional(),
langSmithProject: z.string().optional(),
replacements: Replacements.optional().default({}),
size: z.number().optional().default(20),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ components:
type: string
datasetName:
type: string
evaluatorConnectorId:
type: string
connectorIds:
type: array
items:
Expand All @@ -72,6 +74,8 @@ components:
default: ".alerts-security.alerts-default"
langSmithApiKey:
type: string
langSmithProject:
type: string
replacements:
$ref: "../conversations/common_attributes.schema.yaml#/components/schemas/Replacements"
default: {}
Expand Down
16 changes: 16 additions & 0 deletions x-pack/packages/kbn-elastic-assistant-common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,19 @@ export {
export { transformRawData } from './impl/data_anonymization/transform_raw_data';
export { parseBedrockBuffer, handleBedrockChunk } from './impl/utils/bedrock';
export * from './constants';

/** currently the same shape as "fields" property in the ES response */
export { type MaybeRawData } from './impl/alerts/helpers/types';

/**
* This query returns open and acknowledged (non-building block) alerts in the last 24 hours.
*
* The alerts are ordered by risk score, and then from the most recent to the oldest.
*/
export { getOpenAndAcknowledgedAlertsQuery } from './impl/alerts/get_open_and_acknowledged_alerts_query';

/** Returns the raw data if it valid, or a default if it's not */
export { getRawDataOrDefault } from './impl/alerts/helpers/get_raw_data_or_default';

/** Return true if the provided size is out of range */
export { sizeIsOutOfRange } from './impl/alerts/helpers/size_is_out_of_range';
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import * as i18n from '../../../knowledge_base/translations';
export const MIN_LATEST_ALERTS = 10;
export const MAX_LATEST_ALERTS = 100;
export const TICK_INTERVAL = 10;
export const RANGE_CONTAINER_WIDTH = 300; // px
export const RANGE_CONTAINER_WIDTH = 600; // px
const LABEL_WRAPPER_MIN_WIDTH = 95; // px

interface Props {
Expand Down Expand Up @@ -52,6 +52,7 @@ const AlertsSettingsComponent = ({ knowledgeBase, setUpdatedKnowledgeBaseSetting
<AlertsRange
knowledgeBase={knowledgeBase}
setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings}
value={knowledgeBase.latestAlerts}
/>
<EuiSpacer size="s" />
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const AlertsSettingsManagement: React.FC<Props> = React.memo(
knowledgeBase={knowledgeBase}
setUpdatedKnowledgeBaseSettings={setUpdatedKnowledgeBaseSettings}
compressed={false}
value={knowledgeBase.latestAlerts}
/>
</EuiPanel>
);
Expand Down
Loading

0 comments on commit ad2ac71

Please sign in to comment.