-
-
- ),
- }}
- />
- {notRunningJobIds.length > 0 && (
-
-
-
- {notRunningJobIds.length === 1 ? (
-
- ) : (
+ const notRunningJobIds = useMemo(() => {
+ const selectedJobs = jobs.filter(({ id }) => selectedJobIds.includes(id));
+ return selectedJobs.reduce((acc, job) => {
+ if (!isJobStarted(job.jobState, job.datafeedState)) {
+ acc.push(job.id);
+ }
+ return acc;
+ }, [] as string[]);
+ }, [jobs, selectedJobIds]);
+
+ return (
+ <>
+
acc + (i < array.length - 1 ? ', ' : ', and ') + value
- ),
- }}
+ id="xpack.securitySolution.components.mlJobSelect.machineLearningLink"
+ defaultMessage="Machine Learning"
/>
- )}
-
-
-
- )}
- >
-);
+
+ ),
+ }}
+ />
+ {notRunningJobIds.length > 0 && (
+
+
+
+ {notRunningJobIds.length === 1 ? (
+
+ ) : (
+
+ acc + (i < array.length - 1 ? ', ' : ', and ') + value
+ ),
+ }}
+ />
+ )}
+
+
+
+ )}
+ >
+ );
+});
-export const HelpText = React.memo(HelpTextComponent);
+const warningContainerClassName = css`
+ margin-top: 10px;
+`;
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/index.tsx
index e9c22457d7465..187f72db3562e 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/index.tsx
@@ -5,153 +5,4 @@
* 2.0.
*/
-import React, { useCallback, useMemo } from 'react';
-import type { EuiComboBoxOptionOption } from '@elastic/eui';
-import {
- EuiButton,
- EuiComboBox,
- EuiFlexGroup,
- EuiFlexItem,
- EuiFormRow,
- EuiToolTip,
- EuiText,
-} from '@elastic/eui';
-
-import styled from 'styled-components';
-import { isJobStarted } from '../../../../../common/machine_learning/helpers';
-import type { FieldHook } from '../../../../shared_imports';
-import { getFieldValidityAndErrorMessage } from '../../../../shared_imports';
-import { useSecurityJobs } from '../../../../common/components/ml_popover/hooks/use_security_jobs';
-import { useKibana } from '../../../../common/lib/kibana';
-import { HelpText } from './help_text';
-
-import * as i18n from './translations';
-
-interface MlJobValue {
- id: string;
- description: string;
- name?: string;
-}
-
-const JobDisplayContainer = styled.div`
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
-`;
-
-type MlJobOption = EuiComboBoxOptionOption;
-
-const MlJobSelectEuiFlexGroup = styled(EuiFlexGroup)`
- margin-bottom: 5px;
-`;
-
-const MlJobEuiButton = styled(EuiButton)`
- margin-top: 20px;
-`;
-
-const JobDisplay: React.FC = ({ description, name, id }) => (
-
- {name ?? id}
-
-
- {description}
-
-
-
-);
-
-interface MlJobSelectProps {
- describedByIds: string[];
- field: FieldHook;
-}
-
-const renderJobOption = (option: MlJobOption) => (
-
-);
-
-export const MlJobSelect: React.FC = ({ describedByIds = [], field }) => {
- const jobIds = field.value as string[];
- const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
- const { loading, jobs } = useSecurityJobs();
- const { getUrlForApp, navigateToApp } = useKibana().services.application;
- const mlUrl = getUrlForApp('ml');
- const handleJobSelect = useCallback(
- (selectedJobOptions: MlJobOption[]): void => {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const selectedJobIds = selectedJobOptions.map((option) => option.value!.id);
- field.setValue(selectedJobIds);
- },
- [field]
- );
-
- const jobOptions = jobs.map((job) => ({
- value: {
- id: job.id,
- description: job.description,
- name: job.customSettings?.security_app_display_name,
- },
- // Make sure users can search for id or name.
- // The label contains the name and id because EuiComboBox uses it for the textual search.
- label: `${job.customSettings?.security_app_display_name} ${job.id}`,
- }));
-
- const selectedJobOptions = jobOptions
- .filter((option) => jobIds.includes(option.value.id))
- // 'label' defines what is rendered inside the selected ComboBoxPill
- .map((options) => ({ ...options, label: options.value.name ?? options.value.id }));
-
- const notRunningJobIds = useMemo(() => {
- const selectedJobs = jobs.filter(({ id }) => jobIds.includes(id));
- return selectedJobs.reduce((acc, job) => {
- if (!isJobStarted(job.jobState, job.datafeedState)) {
- acc.push(job.id);
- }
- return acc;
- }, [] as string[]);
- }, [jobs, jobIds]);
-
- return (
-
-
- }
- isInvalid={isInvalid}
- error={errorMessage}
- data-test-subj="mlJobSelect"
- describedByIds={describedByIds}
- >
-
-
-
-
-
-
-
-
- navigateToApp('ml', { openInNewTab: true })}
- >
- {i18n.CREATE_CUSTOM_JOB_BUTTON_TITLE}
-
-
-
- );
-};
+export { MlJobSelect } from './ml_job_select';
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/ml_job_select.test.tsx
similarity index 89%
rename from x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/index.test.tsx
rename to x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/ml_job_select.test.tsx
index 5323ac16bff4f..ddbb6c8b70708 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/index.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/ml_job_select.test.tsx
@@ -21,9 +21,9 @@ describe('MlJobSelect', () => {
it('renders correctly', () => {
const Component = () => {
- const field = useFormFieldMock();
+ const field = useFormFieldMock();
- return ;
+ return ;
};
const wrapper = shallow();
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/ml_job_select.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/ml_job_select.tsx
new file mode 100644
index 0000000000000..160178a5b782d
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/ml_job_select.tsx
@@ -0,0 +1,113 @@
+/*
+ * 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 React from 'react';
+import {
+ EuiComboBox,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiText,
+ EuiToolTip,
+} from '@elastic/eui';
+
+import type { FieldHook } from '../../../../shared_imports';
+import { getFieldValidityAndErrorMessage } from '../../../../shared_imports';
+import { HelpText } from './help_text';
+import * as i18n from './translations';
+import type { SecurityJob } from '../../../../common/components/ml_popover/types';
+import type { MlJobOption, MlJobValue } from './types';
+import * as styles from './styles';
+
+interface MlJobSelectProps {
+ field: FieldHook;
+ shouldShowHelpText?: boolean;
+ loading: boolean;
+ jobs: SecurityJob[];
+}
+
+export const MlJobSelect: React.FC = ({
+ field,
+ shouldShowHelpText = true,
+ loading,
+ jobs,
+}) => {
+ const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
+
+ const selectedJobIds = field.value;
+
+ const handleJobSelect = (selectedJobOptions: MlJobOption[]): void => {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const newlySelectedJobIds = selectedJobOptions.map((option) => option.value!.id);
+ field.setValue(newlySelectedJobIds);
+ };
+
+ const jobOptions = jobs.map((job) => ({
+ value: {
+ id: job.id,
+ description: job.description,
+ name: job.customSettings?.security_app_display_name,
+ },
+ // Make sure users can search for id or name.
+ // The label contains the name and id because EuiComboBox uses it for the textual search.
+ label: `${job.customSettings?.security_app_display_name} ${job.id}`,
+ }));
+
+ const selectedJobOptions = jobOptions
+ .filter((option) => selectedJobIds.includes(option.value.id))
+ // 'label' defines what is rendered inside the selected ComboBoxPill
+ .map((options) => ({ ...options, label: options.value.name ?? options.value.id }));
+
+ return (
+
+
+ }
+ isInvalid={isInvalid}
+ error={errorMessage}
+ data-test-subj="mlJobSelect"
+ >
+
+
+
+
+
+
+
+
+ );
+};
+
+const renderJobOption = (option: MlJobOption) => (
+
+);
+
+const JobDisplay: React.FC = ({ description, name, id }) => (
+
+
{name ?? id}
+
+
+ {description}
+
+
+
+);
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/styles.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/styles.ts
new file mode 100644
index 0000000000000..ffc6d7738fa6a
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/styles.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 { css } from '@emotion/css';
+
+export const mlJobSelectClassName = css`
+ margin-bottom: 5px;
+`;
+
+export const jobDisplayClassName = css`
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+`;
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/translations.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/translations.tsx
index e0beadc019b4f..2d089fea04dfc 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/translations.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/translations.tsx
@@ -7,13 +7,6 @@
import { i18n } from '@kbn/i18n';
-export const CREATE_CUSTOM_JOB_BUTTON_TITLE = i18n.translate(
- 'xpack.securitySolution.detectionEngine.mlSelectJob.createCustomJobButtonTitle',
- {
- defaultMessage: 'Create custom job',
- }
-);
-
export const ML_JOB_SELECT_PLACEHOLDER_TEXT = i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.mlJobSelectPlaceholderText',
{
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/types.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/types.ts
new file mode 100644
index 0000000000000..a10a9da862d2c
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/ml_job_select/types.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { EuiComboBoxOptionOption } from '@elastic/eui';
+
+export interface MlJobValue {
+ id: string;
+ description: string;
+ name?: string;
+}
+
+export type MlJobOption = EuiComboBoxOptionOption;
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/field_configs.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/field_configs.ts
new file mode 100644
index 0000000000000..5bf7500ea1164
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/field_configs.ts
@@ -0,0 +1,91 @@
+/*
+ * 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 { ValidationFunc } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
+import { FIELD_TYPES } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
+import type { ERROR_CODE } from '@kbn/es-ui-shared-plugin/static/forms/helpers/field_validators/types';
+import { isEmpty } from 'lodash';
+import { fieldValidators } from '../../../../shared_imports';
+import * as i18n from './translations';
+
+export const THRESHOLD_FIELD_CONFIG = {
+ type: FIELD_TYPES.COMBO_BOX,
+ label: i18n.THRESHOLD_FIELD_LABEL,
+ helpText: i18n.THRESHOLD_FIELD_HELP_TEXT,
+ validations: [
+ {
+ validator: fieldValidators.maxLengthField({
+ length: 3,
+ message: i18n.THRESHOLD_FIELD_COUNT_VALIDATION_ERROR,
+ }),
+ },
+ ],
+};
+
+export const THRESHOLD_VALUE_CONFIG = {
+ type: FIELD_TYPES.NUMBER,
+ label: i18n.THRESHOLD_VALUE_LABEL,
+ validations: [
+ {
+ validator: fieldValidators.numberGreaterThanField({
+ than: 1,
+ message: i18n.THRESHOLD_VALUE_VALIDATION_ERROR,
+ allowEquality: true,
+ }),
+ },
+ ],
+};
+
+export function getCardinalityFieldConfig(path: string) {
+ return {
+ defaultValue: [] as unknown,
+ fieldsToValidateOnChange: [`${path}.cardinality.field`, `${path}.cardinality.value`],
+ type: FIELD_TYPES.COMBO_BOX,
+ label: i18n.CARDINALITY_FIELD_LABEL,
+ validations: [
+ {
+ validator: (
+ ...args: Parameters
+ ): ReturnType> | undefined => {
+ const [{ formData }] = args;
+
+ if (!isEmpty(formData[`${path}.cardinality.value`])) {
+ return fieldValidators.emptyField(i18n.CARDINALITY_FIELD_MISSING_VALIDATION_ERROR)(
+ ...args
+ );
+ }
+ },
+ },
+ ],
+ helpText: i18n.CARDINALITY_FIELD_HELP_TEXT,
+ };
+}
+
+export function getCardinalityValueConfig(path: string) {
+ return {
+ fieldsToValidateOnChange: [`${path}.cardinality.field`, `${path}.cardinality.value`],
+ type: FIELD_TYPES.NUMBER,
+ label: i18n.CARDINALITY_VALUE_LABEL,
+ validations: [
+ {
+ validator: (
+ ...args: Parameters
+ ): ReturnType> | undefined => {
+ const [{ formData }] = args;
+
+ if (!isEmpty(formData[`${path}.cardinality.field`])) {
+ return fieldValidators.numberGreaterThanField({
+ than: 1,
+ message: i18n.CARDINALITY_VALUE_VALIDATION_ERROR,
+ allowEquality: true,
+ })(...args);
+ }
+ },
+ },
+ ],
+ };
+}
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/index.tsx
new file mode 100644
index 0000000000000..aad44c4a8b842
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/index.tsx
@@ -0,0 +1,8 @@
+/*
+ * 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.
+ */
+
+export { ThresholdEdit } from './threshold_edit';
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/threshold_edit.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/threshold_edit.tsx
new file mode 100644
index 0000000000000..3545bc120f95e
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/threshold_edit.tsx
@@ -0,0 +1,75 @@
+/*
+ * 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 React, { useCallback, useMemo } from 'react';
+import { type FieldSpec } from '@kbn/data-views-plugin/common';
+import { type FieldHook, UseMultiFields } from '../../../../shared_imports';
+import { ThresholdInput } from '../../../rule_creation_ui/components/threshold_input';
+import {
+ THRESHOLD_FIELD_CONFIG,
+ THRESHOLD_VALUE_CONFIG,
+ getCardinalityFieldConfig,
+ getCardinalityValueConfig,
+} from './field_configs';
+
+interface ThresholdEditProps {
+ path: string;
+ esFields: FieldSpec[];
+}
+
+export function ThresholdEdit({ path, esFields }: ThresholdEditProps): JSX.Element {
+ const aggregatableFields = useMemo(
+ () => esFields.filter((field) => field.aggregatable === true),
+ [esFields]
+ );
+
+ const ThresholdInputChildren = useCallback(
+ ({
+ thresholdField,
+ thresholdValue,
+ thresholdCardinalityField,
+ thresholdCardinalityValue,
+ }: Record) => (
+
+ ),
+ [aggregatableFields]
+ );
+
+ const cardinalityFieldConfig = useMemo(() => getCardinalityFieldConfig(path), [path]);
+ const cardinalityValueConfig = useMemo(() => getCardinalityValueConfig(path), [path]);
+
+ return (
+
+ {ThresholdInputChildren}
+
+ );
+}
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/translations.ts
new file mode 100644
index 0000000000000..26981afd15e3a
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/threshold_edit/translations.ts
@@ -0,0 +1,78 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const THRESHOLD_FIELD_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdFieldLabel',
+ {
+ defaultMessage: 'Group by',
+ }
+);
+
+export const THRESHOLD_FIELD_HELP_TEXT = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdFieldHelpText',
+ {
+ defaultMessage: "Select fields to group by. Fields are joined together with 'AND'",
+ }
+);
+
+export const THRESHOLD_FIELD_COUNT_VALIDATION_ERROR = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.validations.thresholdFieldFieldData.arrayLengthGreaterThanMaxErrorMessage',
+ {
+ defaultMessage: 'Number of fields must be 3 or less.',
+ }
+);
+
+export const THRESHOLD_VALUE_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThresholdValueLabel',
+ {
+ defaultMessage: 'Threshold',
+ }
+);
+
+export const THRESHOLD_VALUE_VALIDATION_ERROR = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.validations.thresholdValueFieldData.numberGreaterThanOrEqualOneErrorMessage',
+ {
+ defaultMessage: 'Value must be greater than or equal to one.',
+ }
+);
+
+export const CARDINALITY_FIELD_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdCardinalityFieldLabel',
+ {
+ defaultMessage: 'Count',
+ }
+);
+
+export const CARDINALITY_FIELD_MISSING_VALIDATION_ERROR = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.validations.thresholdCardinalityFieldFieldData.thresholdCardinalityFieldNotSuppliedMessage',
+ {
+ defaultMessage: 'A Cardinality Field is required.',
+ }
+);
+
+export const CARDINALITY_FIELD_HELP_TEXT = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdFieldCardinalityFieldHelpText',
+ {
+ defaultMessage: 'Select a field to check cardinality',
+ }
+);
+
+export const CARDINALITY_VALUE_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdCardinalityValueFieldLabel',
+ {
+ defaultMessage: 'Unique values',
+ }
+);
+
+export const CARDINALITY_VALUE_VALIDATION_ERROR = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.validations.thresholdCardinalityValueFieldData.numberGreaterThanOrEqualOneErrorMessage',
+ {
+ defaultMessage: 'Value must be greater than or equal to one.',
+ }
+);
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx
index b45f1a50414ac..90afe8d95fa37 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx
@@ -412,6 +412,8 @@ describe('description_step', () => {
});
describe('threshold', () => {
+ const thresholdLabel = 'Threshold';
+
test('returns threshold description when threshold exist and field is empty', () => {
const mockThreshold = {
threshold: {
@@ -421,13 +423,13 @@ describe('description_step', () => {
};
const result: ListItems[] = getDescriptionItem(
'threshold',
- 'Threshold label',
+ thresholdLabel,
mockThreshold,
mockFilterManager,
mockLicenseService
);
- expect(result[0].title).toEqual('Threshold label');
+ expect(result[0].title).toEqual(thresholdLabel);
expect(React.isValidElement(result[0].description)).toBeTruthy();
expect(mount(result[0].description as React.ReactElement).html()).toContain(
'All results >= 100'
@@ -443,13 +445,13 @@ describe('description_step', () => {
};
const result: ListItems[] = getDescriptionItem(
'threshold',
- 'Threshold label',
+ thresholdLabel,
mockThreshold,
mockFilterManager,
mockLicenseService
);
- expect(result[0].title).toEqual('Threshold label');
+ expect(result[0].title).toEqual(thresholdLabel);
expect(React.isValidElement(result[0].description)).toBeTruthy();
expect(mount(result[0].description as React.ReactElement).html()).toEqual(
'Results aggregated by user.name >= 100'
@@ -469,13 +471,13 @@ describe('description_step', () => {
};
const result: ListItems[] = getDescriptionItem(
'threshold',
- 'Threshold label',
+ thresholdLabel,
mockThreshold,
mockFilterManager,
mockLicenseService
);
- expect(result[0].title).toEqual('Threshold label');
+ expect(result[0].title).toEqual(thresholdLabel);
expect(React.isValidElement(result[0].description)).toBeTruthy();
expect(mount(result[0].description as React.ReactElement).html()).toEqual(
'Results aggregated by user.name >= 100'
@@ -495,13 +497,13 @@ describe('description_step', () => {
};
const result: ListItems[] = getDescriptionItem(
'threshold',
- 'Threshold label',
+ thresholdLabel,
mockThreshold,
mockFilterManager,
mockLicenseService
);
- expect(result[0].title).toEqual('Threshold label');
+ expect(result[0].title).toEqual(thresholdLabel);
expect(React.isValidElement(result[0].description)).toBeTruthy();
expect(mount(result[0].description as React.ReactElement).html()).toContain(
'Results aggregated by user.name >= 100 when unique values count of host.test_value >= 10'
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx
index a91487afc4486..46ed1f18f70ac 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx
@@ -72,8 +72,11 @@ import {
ALERT_SUPPRESSION_MISSING_FIELDS_FIELD_NAME,
} from '../../../rule_creation/components/alert_suppression_edit';
import { THRESHOLD_ALERT_SUPPRESSION_ENABLED } from '../../../rule_creation/components/threshold_alert_suppression_edit';
+import { THRESHOLD_VALUE_LABEL } from '../../../rule_creation/components/threshold_edit/translations';
import { NEW_TERMS_FIELDS_LABEL } from '../../../rule_creation/components/new_terms_fields_edit/translations';
import { HISTORY_WINDOW_START_LABEL } from '../../../rule_creation/components/history_window_start_edit/translations';
+import { MACHINE_LEARNING_JOB_ID_LABEL } from '../../../rule_creation/components/machine_learning_job_id_edit/translations';
+import { ANOMALY_THRESHOLD_LABEL } from '../../../rule_creation/components/anomaly_threshold_edit/translations';
import type { FieldValueQueryBar } from '../query_bar_field';
const DescriptionListContainer = styled(EuiDescriptionList)`
@@ -108,10 +111,7 @@ export const StepRuleDescriptionComponent = ({
if (key === 'machineLearningJobId') {
return [
...acc,
- buildMlJobsDescription(
- get(key, data) as string[],
- (get(key, schema) as { label: string }).label
- ),
+ buildMlJobsDescription(get(key, data) as string[], MACHINE_LEARNING_JOB_ID_LABEL),
];
}
@@ -286,7 +286,7 @@ export const getDescriptionItem = (
return buildThreatDescription({ label, threat: filterEmptyThreats(threats) });
} else if (field === 'threshold') {
const threshold = get(field, data);
- return buildThresholdDescription(label, threshold);
+ return buildThresholdDescription(THRESHOLD_VALUE_LABEL, threshold);
} else if (field === 'references') {
const urls: string[] = get(field, data);
return buildUrlsDescription(label, urls);
@@ -362,6 +362,9 @@ export const getDescriptionItem = (
} else if (field === 'maxSignals') {
const value: number | undefined = get(field, data);
return value ? [{ title: label, description: value }] : [];
+ } else if (field === 'anomalyThreshold') {
+ const value: number | undefined = get(field, data);
+ return value ? [{ title: ANOMALY_THRESHOLD_LABEL, description: value }] : [];
} else if (field === 'historyWindowSize') {
const value: number = get(field, data);
return value ? [{ title: HISTORY_WINDOW_START_LABEL, description: value }] : [];
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts
index fcbdfdf4f86b7..c45022ebb96b5 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts
@@ -117,7 +117,7 @@ export const getIsRulePreviewDisabled = ({
dataSourceType: DataSourceType;
threatIndex: string[];
threatMapping: ThreatMapping;
- machineLearningJobId: string[];
+ machineLearningJobId: string[] | undefined;
queryBar: FieldValueQueryBar;
newTermsFields: string[];
}) => {
@@ -125,7 +125,7 @@ export const getIsRulePreviewDisabled = ({
return isEsqlPreviewDisabled({ isQueryBarValid, queryBar });
}
if (ruleType === 'machine_learning') {
- return machineLearningJobId.length === 0;
+ return !machineLearningJobId ?? machineLearningJobId?.length === 0;
}
if (
!isQueryBarValid ||
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx
index 2d2ef8c8930d6..6e20138256d7f 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx
@@ -50,7 +50,7 @@ const CommonUseField = getUseField({ component: Field });
interface StepAboutRuleProps extends RuleStepProps {
ruleType: Type;
- machineLearningJobId: string[];
+ machineLearningJobId: string[] | undefined;
index: string[];
dataViewId: string | undefined;
timestampOverride: string;
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx
index fa7f9a8952cac..65384f838ac9b 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx
@@ -830,7 +830,6 @@ function TestForm({
shouldLoadQueryDynamically={stepDefineDefaultValue.shouldLoadQueryDynamically}
queryBarTitle=""
queryBarSavedId=""
- thresholdFields={[]}
{...formProps}
/>