diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
index c14471991ccd3..dbf53a9f0a359 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { useEffect } from 'react';
+import React, { useEffect, FunctionComponent } from 'react';
import { act } from 'react-dom/test-utils';
import { registerTestBed, TestBed } from '../shared_imports';
@@ -237,4 +237,64 @@ describe('', () => {
expect(serializer).not.toBeCalled();
});
});
+
+ describe('custom components', () => {
+ interface MyForm {
+ name: string;
+ }
+
+ let formHook: FormHook | null = null;
+
+ beforeEach(() => {
+ formHook = null;
+ });
+
+ const onFormHook = (_form: FormHook) => {
+ formHook = _form;
+ };
+
+ const TestComp = ({
+ component,
+ onForm,
+ }: {
+ component: FunctionComponent;
+ onForm: (form: FormHook) => void;
+ }) => {
+ const { form } = useForm();
+
+ useEffect(() => {
+ onForm(form);
+ }, [onForm, form]);
+
+ return (
+
+ );
+ };
+
+ it('allows function components', () => {
+ const Component = () => ;
+ const setup = registerTestBed(TestComp, {
+ defaultProps: { onForm: onFormHook, component: Component },
+ memoryRouter: { wrapComponent: false },
+ });
+ const testBed = setup() as TestBed;
+
+ expect(testBed.exists('function-component')).toEqual(true);
+ expect(formHook?.getFormData()).toEqual({ name: 'myName' });
+ });
+
+ it('allows memoized function components', () => {
+ const Component = React.memo(() => );
+ const setup = registerTestBed(TestComp, {
+ defaultProps: { onForm: onFormHook, component: Component },
+ memoryRouter: { wrapComponent: false },
+ });
+ const testBed = setup() as TestBed;
+
+ expect(testBed.exists('memoized-component')).toEqual(true);
+ expect(formHook?.getFormData()).toEqual({ name: 'myName' });
+ });
+ });
});
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx
index 3a3ab0dab5e93..6b913f246abbb 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx
@@ -49,7 +49,7 @@ function UseFieldComp(props: Props) {
} = props;
const form = useFormContext();
- const componentToRender = component ?? 'input';
+ const ComponentToRender = component ?? 'input';
// For backward compatibility we merge the "componentProps" prop into the "rest"
const propsToForward =
componentProps !== undefined ? { ...componentProps, ...rest } : { ...rest };
@@ -91,9 +91,9 @@ function UseFieldComp(props: Props) {
return children!(field);
}
- if (componentToRender === 'input') {
+ if (ComponentToRender === 'input') {
return (
- (props: Props) {
);
}
- return componentToRender({ field, ...propsToForward });
+ return ;
}
export const UseField = React.memo(UseFieldComp) as typeof UseFieldComp;
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
index 498b561a818f2..f002e13a07cf1 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
@@ -130,7 +130,7 @@ export type Query = t.TypeOf;
export const queryOrUndefined = t.union([query, t.undefined]);
export type QueryOrUndefined = t.TypeOf;
-export const language = t.keyof({ kuery: null, lucene: null });
+export const language = t.keyof({ eql: null, kuery: null, lucene: null });
export type Language = t.TypeOf;
export const languageOrUndefined = t.union([language, t.undefined]);
@@ -294,6 +294,7 @@ export const toOrUndefined = t.union([to, t.undefined]);
export type ToOrUndefined = t.TypeOf;
export const type = t.keyof({
+ eql: null,
machine_learning: null,
query: null,
saved_query: null,
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts
index 6a51f724fc9e6..c244b9aca9ad0 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts
@@ -4,10 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { isMlRule } from '../../../machine_learning/helpers';
+import { isThresholdRule } from '../../utils';
import { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema';
export const validateAnomalyThreshold = (rule: AddPrepackagedRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.anomaly_threshold == null) {
return ['when "type" is "machine_learning" anomaly_threshold is required'];
} else {
@@ -19,7 +21,7 @@ export const validateAnomalyThreshold = (rule: AddPrepackagedRulesSchema): strin
};
export const validateQuery = (rule: AddPrepackagedRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.query != null) {
return ['when "type" is "machine_learning", "query" cannot be set'];
} else {
@@ -31,7 +33,7 @@ export const validateQuery = (rule: AddPrepackagedRulesSchema): string[] => {
};
export const validateLanguage = (rule: AddPrepackagedRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.language != null) {
return ['when "type" is "machine_learning", "language" cannot be set'];
} else {
@@ -55,7 +57,7 @@ export const validateSavedId = (rule: AddPrepackagedRulesSchema): string[] => {
};
export const validateMachineLearningJobId = (rule: AddPrepackagedRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.machine_learning_job_id == null) {
return ['when "type" is "machine_learning", "machine_learning_job_id" is required'];
} else {
@@ -93,7 +95,7 @@ export const validateTimelineTitle = (rule: AddPrepackagedRulesSchema): string[]
};
export const validateThreshold = (rule: AddPrepackagedRulesSchema): string[] => {
- if (rule.type === 'threshold') {
+ if (isThresholdRule(rule.type)) {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts
index af665ff8c81d2..91b14fa9b999c 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts
@@ -4,10 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { isMlRule } from '../../../machine_learning/helpers';
+import { isThresholdRule } from '../../utils';
import { CreateRulesSchema } from './create_rules_schema';
export const validateAnomalyThreshold = (rule: CreateRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.anomaly_threshold == null) {
return ['when "type" is "machine_learning" anomaly_threshold is required'];
} else {
@@ -19,7 +21,7 @@ export const validateAnomalyThreshold = (rule: CreateRulesSchema): string[] => {
};
export const validateQuery = (rule: CreateRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.query != null) {
return ['when "type" is "machine_learning", "query" cannot be set'];
} else {
@@ -31,7 +33,7 @@ export const validateQuery = (rule: CreateRulesSchema): string[] => {
};
export const validateLanguage = (rule: CreateRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.language != null) {
return ['when "type" is "machine_learning", "language" cannot be set'];
} else {
@@ -55,7 +57,7 @@ export const validateSavedId = (rule: CreateRulesSchema): string[] => {
};
export const validateMachineLearningJobId = (rule: CreateRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.machine_learning_job_id == null) {
return ['when "type" is "machine_learning", "machine_learning_job_id" is required'];
} else {
@@ -93,7 +95,7 @@ export const validateTimelineTitle = (rule: CreateRulesSchema): string[] => {
};
export const validateThreshold = (rule: CreateRulesSchema): string[] => {
- if (rule.type === 'threshold') {
+ if (isThresholdRule(rule.type)) {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts
index 269181449e9e9..87264d84fd73a 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts
@@ -4,10 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { isMlRule } from '../../../machine_learning/helpers';
+import { isThresholdRule } from '../../utils';
import { ImportRulesSchema } from './import_rules_schema';
export const validateAnomalyThreshold = (rule: ImportRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.anomaly_threshold == null) {
return ['when "type" is "machine_learning" anomaly_threshold is required'];
} else {
@@ -19,7 +21,7 @@ export const validateAnomalyThreshold = (rule: ImportRulesSchema): string[] => {
};
export const validateQuery = (rule: ImportRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.query != null) {
return ['when "type" is "machine_learning", "query" cannot be set'];
} else {
@@ -31,7 +33,7 @@ export const validateQuery = (rule: ImportRulesSchema): string[] => {
};
export const validateLanguage = (rule: ImportRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.language != null) {
return ['when "type" is "machine_learning", "language" cannot be set'];
} else {
@@ -55,7 +57,7 @@ export const validateSavedId = (rule: ImportRulesSchema): string[] => {
};
export const validateMachineLearningJobId = (rule: ImportRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.machine_learning_job_id == null) {
return ['when "type" is "machine_learning", "machine_learning_job_id" is required'];
} else {
@@ -93,7 +95,7 @@ export const validateTimelineTitle = (rule: ImportRulesSchema): string[] => {
};
export const validateThreshold = (rule: ImportRulesSchema): string[] => {
- if (rule.type === 'threshold') {
+ if (isThresholdRule(rule.type)) {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts
index a229771a7c05c..c974f73926c21 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts
@@ -4,10 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { isMlRule } from '../../../machine_learning/helpers';
+import { isThresholdRule } from '../../utils';
import { PatchRulesSchema } from './patch_rules_schema';
export const validateQuery = (rule: PatchRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.query != null) {
return ['when "type" is "machine_learning", "query" cannot be set'];
} else {
@@ -19,7 +21,7 @@ export const validateQuery = (rule: PatchRulesSchema): string[] => {
};
export const validateLanguage = (rule: PatchRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.language != null) {
return ['when "type" is "machine_learning", "language" cannot be set'];
} else {
@@ -67,7 +69,7 @@ export const validateId = (rule: PatchRulesSchema): string[] => {
};
export const validateThreshold = (rule: PatchRulesSchema): string[] => {
- if (rule.type === 'threshold') {
+ if (isThresholdRule(rule.type)) {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.ts
index 44182d250c801..5f297fb9688fc 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.ts
@@ -4,10 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { isMlRule } from '../../../machine_learning/helpers';
+import { isThresholdRule } from '../../utils';
import { UpdateRulesSchema } from './update_rules_schema';
export const validateAnomalyThreshold = (rule: UpdateRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.anomaly_threshold == null) {
return ['when "type" is "machine_learning" anomaly_threshold is required'];
} else {
@@ -19,7 +21,7 @@ export const validateAnomalyThreshold = (rule: UpdateRulesSchema): string[] => {
};
export const validateQuery = (rule: UpdateRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.query != null) {
return ['when "type" is "machine_learning", "query" cannot be set'];
} else {
@@ -31,7 +33,7 @@ export const validateQuery = (rule: UpdateRulesSchema): string[] => {
};
export const validateLanguage = (rule: UpdateRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.language != null) {
return ['when "type" is "machine_learning", "language" cannot be set'];
} else {
@@ -55,7 +57,7 @@ export const validateSavedId = (rule: UpdateRulesSchema): string[] => {
};
export const validateMachineLearningJobId = (rule: UpdateRulesSchema): string[] => {
- if (rule.type === 'machine_learning') {
+ if (isMlRule(rule.type)) {
if (rule.machine_learning_job_id == null) {
return ['when "type" is "machine_learning", "machine_learning_job_id" is required'];
} else {
@@ -103,7 +105,7 @@ export const validateId = (rule: UpdateRulesSchema): string[] => {
};
export const validateThreshold = (rule: UpdateRulesSchema): string[] => {
- if (rule.type === 'threshold') {
+ if (isThresholdRule(rule.type)) {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts
index b3f9096b51483..36fc063761840 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts
@@ -633,6 +633,16 @@ describe('rules_schema', () => {
expect(fields.length).toEqual(2);
});
+ test('should return two fields for a rule of type "eql"', () => {
+ const fields = addQueryFields({ type: 'eql' });
+ expect(fields.length).toEqual(2);
+ });
+
+ test('should return two fields for a rule of type "threshold"', () => {
+ const fields = addQueryFields({ type: 'threshold' });
+ expect(fields.length).toEqual(2);
+ });
+
test('should return two fields for a rule of type "saved_query"', () => {
const fields = addQueryFields({ type: 'saved_query' });
expect(fields.length).toEqual(2);
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts
index 04df25d805f9e..c26a7efb0c288 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts
@@ -8,9 +8,9 @@ import * as t from 'io-ts';
import { isObject } from 'lodash/fp';
import { Either, left, fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
-import { typeAndTimelineOnlySchema, TypeAndTimelineOnly } from './type_timeline_only_schema';
-import { isMlRule } from '../../../machine_learning/helpers';
+import { isMlRule } from '../../../machine_learning/helpers';
+import { isThresholdRule } from '../../utils';
import {
actions,
anomaly_threshold,
@@ -66,6 +66,7 @@ import {
DefaultRiskScoreMappingArray,
DefaultSeverityMappingArray,
} from '../types';
+import { typeAndTimelineOnlySchema, TypeAndTimelineOnly } from './type_timeline_only_schema';
/**
* This is the required fields for the rules schema response. Put all required properties on
@@ -205,7 +206,7 @@ export const addTimelineTitle = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mi
};
export const addQueryFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => {
- if (['query', 'saved_query', 'threshold'].includes(typeAndTimelineOnly.type)) {
+ if (['eql', 'query', 'saved_query', 'threshold'].includes(typeAndTimelineOnly.type)) {
return [
t.exact(t.type({ query: dependentRulesSchema.props.query })),
t.exact(t.type({ language: dependentRulesSchema.props.language })),
@@ -229,7 +230,7 @@ export const addMlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[]
};
export const addThresholdFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => {
- if (typeAndTimelineOnly.type === 'threshold') {
+ if (isThresholdRule(typeAndTimelineOnly.type)) {
return [
t.exact(t.type({ threshold: dependentRulesSchema.props.threshold })),
t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })),
diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
index a72d641fbaf78..170d28cb5a725 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
@@ -17,4 +17,6 @@ export const hasNestedEntry = (entries: EntriesArray): boolean => {
return found.length > 0;
};
-export const isThresholdRule = (ruleType: Type) => ruleType === 'threshold';
+export const isEqlRule = (ruleType: Type | undefined) => ruleType === 'eql';
+export const isThresholdRule = (ruleType: Type | undefined) => ruleType === 'threshold';
+export const isQueryRule = (ruleType: Type | undefined) => ruleType === 'query';
diff --git a/x-pack/plugins/security_solution/common/machine_learning/helpers.ts b/x-pack/plugins/security_solution/common/machine_learning/helpers.ts
index 73dc30f238c7e..73b14afb5d0c9 100644
--- a/x-pack/plugins/security_solution/common/machine_learning/helpers.ts
+++ b/x-pack/plugins/security_solution/common/machine_learning/helpers.ts
@@ -23,4 +23,4 @@ export const isJobFailed = (jobState: string, datafeedState: string): boolean =>
return failureStates.includes(jobState) || failureStates.includes(datafeedState);
};
-export const isMlRule = (ruleType: Type) => ruleType === 'machine_learning';
+export const isMlRule = (ruleType: Type | undefined) => ruleType === 'machine_learning';
diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts
new file mode 100644
index 0000000000000..c65cd8406099a
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts
@@ -0,0 +1,167 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { eqlRule } from '../objects/rule';
+
+import {
+ CUSTOM_RULES_BTN,
+ RISK_SCORE,
+ RULE_NAME,
+ RULES_ROW,
+ RULES_TABLE,
+ SEVERITY,
+} from '../screens/alerts_detection_rules';
+import {
+ ABOUT_FALSE_POSITIVES,
+ ABOUT_INVESTIGATION_NOTES,
+ ABOUT_MITRE,
+ ABOUT_RISK,
+ ABOUT_RULE_DESCRIPTION,
+ ABOUT_SEVERITY,
+ ABOUT_STEP,
+ ABOUT_TAGS,
+ ABOUT_URLS,
+ DEFINITION_CUSTOM_QUERY,
+ DEFINITION_INDEX_PATTERNS,
+ DEFINITION_TIMELINE,
+ DEFINITION_STEP,
+ INVESTIGATION_NOTES_MARKDOWN,
+ INVESTIGATION_NOTES_TOGGLE,
+ RULE_ABOUT_DETAILS_HEADER_TOGGLE,
+ RULE_NAME_HEADER,
+ SCHEDULE_LOOPBACK,
+ SCHEDULE_RUNS,
+ SCHEDULE_STEP,
+} from '../screens/rule_details';
+
+import {
+ goToManageAlertsDetectionRules,
+ waitForAlertsIndexToBeCreated,
+ waitForAlertsPanelToBeLoaded,
+} from '../tasks/alerts';
+import {
+ changeToThreeHundredRowsPerPage,
+ filterByCustomRules,
+ goToCreateNewRule,
+ goToRuleDetails,
+ waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded,
+ waitForRulesToBeLoaded,
+} from '../tasks/alerts_detection_rules';
+import {
+ createAndActivateRule,
+ fillAboutRuleAndContinue,
+ selectEqlRuleType,
+ fillDefineEqlRuleAndContinue,
+} from '../tasks/create_new_rule';
+import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
+import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
+
+import { DETECTIONS_URL } from '../urls/navigation';
+
+describe('Detection rules, EQL', () => {
+ before(() => {
+ esArchiverLoad('timeline');
+ });
+
+ after(() => {
+ esArchiverUnload('timeline');
+ });
+
+ it('Creates and activates a new EQL rule', () => {
+ loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
+ waitForAlertsPanelToBeLoaded();
+ waitForAlertsIndexToBeCreated();
+ goToManageAlertsDetectionRules();
+ waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded();
+ goToCreateNewRule();
+ selectEqlRuleType();
+ fillDefineEqlRuleAndContinue(eqlRule);
+ fillAboutRuleAndContinue(eqlRule);
+ createAndActivateRule();
+
+ cy.get(CUSTOM_RULES_BTN).invoke('text').should('eql', 'Custom rules (1)');
+
+ changeToThreeHundredRowsPerPage();
+ waitForRulesToBeLoaded();
+
+ const expectedNumberOfRules = 1;
+ cy.get(RULES_TABLE).then(($table) => {
+ cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules);
+ });
+
+ filterByCustomRules();
+
+ cy.get(RULES_TABLE).then(($table) => {
+ cy.wrap($table.find(RULES_ROW).length).should('eql', 1);
+ });
+ cy.get(RULE_NAME).invoke('text').should('eql', eqlRule.name);
+ cy.get(RISK_SCORE).invoke('text').should('eql', eqlRule.riskScore);
+ cy.get(SEVERITY).invoke('text').should('eql', eqlRule.severity);
+ cy.get('[data-test-subj="rule-switch"]').should('have.attr', 'aria-checked', 'true');
+
+ goToRuleDetails();
+
+ let expectedUrls = '';
+ eqlRule.referenceUrls.forEach((url) => {
+ expectedUrls = expectedUrls + url;
+ });
+ let expectedFalsePositives = '';
+ eqlRule.falsePositivesExamples.forEach((falsePositive) => {
+ expectedFalsePositives = expectedFalsePositives + falsePositive;
+ });
+ let expectedTags = '';
+ eqlRule.tags.forEach((tag) => {
+ expectedTags = expectedTags + tag;
+ });
+ let expectedMitre = '';
+ eqlRule.mitre.forEach((mitre) => {
+ expectedMitre = expectedMitre + mitre.tactic;
+ mitre.techniques.forEach((technique) => {
+ expectedMitre = expectedMitre + technique;
+ });
+ });
+ const expectedIndexPatterns = [
+ 'apm-*-transaction*',
+ 'auditbeat-*',
+ 'endgame-*',
+ 'filebeat-*',
+ 'logs-*',
+ 'packetbeat-*',
+ 'winlogbeat-*',
+ ];
+
+ cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${eqlRule.name} Beta`);
+
+ cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', eqlRule.description);
+ cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', eqlRule.severity);
+ cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', eqlRule.riskScore);
+ cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls);
+ cy.get(ABOUT_STEP)
+ .eq(ABOUT_FALSE_POSITIVES)
+ .invoke('text')
+ .should('eql', expectedFalsePositives);
+ cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre);
+ cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags);
+
+ cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
+ cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN);
+
+ cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => {
+ cy.wrap(patterns).each((pattern, index) => {
+ cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]);
+ });
+ });
+ cy.get(DEFINITION_STEP)
+ .eq(DEFINITION_CUSTOM_QUERY)
+ .invoke('text')
+ .should('eql', `${eqlRule.customQuery} `);
+ cy.get(DEFINITION_STEP).eq(2).invoke('text').should('eql', 'Event Correlation');
+ cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None');
+
+ cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m');
+ cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m');
+ });
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts
index df6b792296f9d..0624606fe8481 100644
--- a/x-pack/plugins/security_solution/cypress/objects/rule.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts
@@ -156,3 +156,17 @@ export const machineLearningRule: MachineLearningRule = {
mitre: [mitre1],
note: '# test markdown',
};
+
+export const eqlRule: CustomRule = {
+ customQuery: 'process where process_name == "explorer.exe"',
+ name: 'New EQL Rule',
+ description: 'New EQL rule description.',
+ severity: 'High',
+ riskScore: '17',
+ tags: ['test', 'newRule'],
+ referenceUrls: ['https://www.google.com/', 'https://elastic.co/'],
+ falsePositivesExamples: ['False1', 'False2'],
+ mitre: [mitre1, mitre2],
+ note: '# test markdown',
+ timelineId: '0162c130-78be-11ea-9718-118a926974a4',
+};
diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
index 397d0c0142179..1c25ed88c3bee 100644
--- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
@@ -30,6 +30,10 @@ export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]';
export const DEFINE_EDIT_BUTTON = '[data-test-subj="edit-define-rule"]';
+export const EQL_TYPE = '[data-test-subj="eqlRuleType"]';
+
+export const EQL_QUERY_INPUT = '[data-test-subj="eqlQueryBarTextInput"]';
+
export const IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK =
'[data-test-subj="importQueryFromSavedTimeline"]';
diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
index 3fa300ce9d8d0..f26a77171462c 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
@@ -50,6 +50,8 @@ import {
THRESHOLD_TYPE,
DEFINE_EDIT_BUTTON,
ABOUT_EDIT_BUTTON,
+ EQL_TYPE,
+ EQL_QUERY_INPUT,
} from '../screens/create_new_rule';
import { TIMELINE } from '../screens/timeline';
@@ -209,6 +211,14 @@ export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => {
cy.get(CUSTOM_QUERY_INPUT).should('not.exist');
};
+export const fillDefineEqlRuleAndContinue = (rule: CustomRule) => {
+ cy.get(EQL_QUERY_INPUT).type(rule.customQuery);
+ cy.get(EQL_QUERY_INPUT).invoke('text').should('eq', rule.customQuery);
+ cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
+
+ cy.get(EQL_QUERY_INPUT).should('not.exist');
+};
+
export const fillDefineMachineLearningRuleAndContinue = (rule: MachineLearningRule) => {
cy.get(MACHINE_LEARNING_DROPDOWN).click({ force: true });
cy.contains(MACHINE_LEARNING_LIST, rule.machineLearningJob).click();
@@ -227,3 +237,7 @@ export const selectMachineLearningRuleType = () => {
export const selectThresholdRuleType = () => {
cy.get(THRESHOLD_TYPE).click({ force: true });
};
+
+export const selectEqlRuleType = () => {
+ cy.get(EQL_TYPE).click({ force: true });
+};
diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx
index 5a4eaad72ac32..8c186addf783d 100644
--- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx
+++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx
@@ -77,11 +77,11 @@ const TestProvidersComponent: React.FC = ({
export const TestProviders = React.memo(TestProvidersComponent);
-export const useFormFieldMock = (options?: Partial): FieldHook => {
+export const useFormFieldMock = (options?: Partial>): FieldHook => {
return {
path: 'path',
type: 'type',
- value: [],
+ value: ('mockedValue' as unknown) as T,
isPristine: false,
isValidating: false,
isValidated: false,
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/add_item_form/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/add_item_form/index.test.tsx
index 890e66c8767c4..362dfed4b8e72 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/add_item_form/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/add_item_form/index.test.tsx
@@ -13,7 +13,7 @@ import { useFormFieldMock } from '../../../../common/mock/test_providers';
describe('AddItem', () => {
it('renders correctly', () => {
const Component = () => {
- const field = useFormFieldMock();
+ const field = useFormFieldMock({ value: [] });
return (
{
+ let mockField: EqlQueryBarProps['field'];
+
+ beforeEach(() => {
+ mockField = useFormFieldMock({
+ value: mockQueryBar,
+ });
+ });
+
+ it('renders correctly', () => {
+ const wrapper = shallow();
+
+ expect(wrapper.find('[data-test-subj="myQueryBar"]')).toHaveLength(1);
+ });
+
+ it('sets the field value on input change', () => {
+ const wrapper = mount();
+
+ wrapper
+ .find('[data-test-subj="eqlQueryBarTextInput"]')
+ .first()
+ .simulate('change', { target: { value: 'newQuery' } });
+
+ const expected = {
+ filters: [],
+ query: {
+ query: 'newQuery',
+ language: 'eql',
+ },
+ };
+
+ expect(mockField.setValue).toHaveBeenCalledWith(expected);
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx
new file mode 100644
index 0000000000000..e3f33ea9b9b87
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, useCallback, ChangeEvent } from 'react';
+import { EuiFormRow, EuiTextArea } from '@elastic/eui';
+
+import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_imports';
+import { DefineStepRule } from '../../../pages/detection_engine/rules/types';
+
+export interface EqlQueryBarProps {
+ dataTestSubj: string;
+ field: FieldHook;
+ idAria?: string;
+}
+
+export const EqlQueryBar: FC = ({ dataTestSubj, field, idAria }) => {
+ const { setValue } = field;
+ const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
+ const fieldValue = field.value.query.query as string;
+
+ const handleChange = useCallback(
+ (e: ChangeEvent) => {
+ const newQuery = e.target.value;
+
+ setValue({
+ filters: [],
+ query: {
+ query: newQuery,
+ language: 'eql',
+ },
+ });
+ },
+ [setValue]
+ );
+
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/index.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/index.ts
new file mode 100644
index 0000000000000..fcbe069c1f57f
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { EqlQueryBar } from './eql_query_bar';
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/index.test.tsx
index ecf1bda807b68..23b3519cee582 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/index.test.tsx
@@ -13,7 +13,7 @@ import { useFormFieldMock } from '../../../../common/mock';
describe('AddMitreThreat', () => {
it('renders correctly', () => {
const Component = () => {
- const field = useFormFieldMock();
+ const field = useFormFieldMock({ value: [] });
return (
= ({
},
[field]
);
+ const setEql = useCallback(() => setType('eql'), [setType]);
const setMl = useCallback(() => setType('machine_learning'), [setType]);
const setQuery = useCallback(() => setType('query'), [setType]);
const setThreshold = useCallback(() => setType('threshold'), [setType]);
@@ -45,11 +50,20 @@ export const SelectRuleType: React.FC = ({
path: '#/management/stack/license_management',
});
+ const eqlSelectableConfig = useMemo(
+ () => ({
+ isDisabled: isReadOnly,
+ onClick: setEql,
+ isSelected: isEqlRule(ruleType),
+ }),
+ [isReadOnly, ruleType, setEql]
+ );
+
const querySelectableConfig = useMemo(
() => ({
isDisabled: isReadOnly,
onClick: setQuery,
- isSelected: !isMlRule(ruleType) && !isThresholdRule(ruleType),
+ isSelected: isQueryRule(ruleType),
}),
[isReadOnly, ruleType, setQuery]
);
@@ -114,6 +128,16 @@ export const SelectRuleType: React.FC = ({
selectable={thresholdSelectableConfig}
/>
+
+ }
+ isDisabled={eqlSelectableConfig.isDisabled && !eqlSelectableConfig.isSelected}
+ selectable={eqlSelectableConfig}
+ />
+
);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/translations.ts
index 3b85a7dfc765c..e7b231ca74958 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/translations.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/translations.ts
@@ -6,6 +6,21 @@
import { i18n } from '@kbn/i18n';
+export const EQL_TYPE_TITLE = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.ruleTypeField.eqlTypeTitle',
+ {
+ defaultMessage: 'Event Correlation',
+ }
+);
+
+export const EQL_TYPE_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.ruleTypeField.eqlTypeDescription',
+ {
+ defaultMessage:
+ 'Use Event Query Language (EQL) to match events, generate sequences, and stack data',
+ }
+);
+
export const QUERY_TYPE_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.ruleTypeField.queryTypeTitle',
{
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx
index 158f323b739e6..7846f0c406668 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx
@@ -46,6 +46,8 @@ import {
} from '../../../../shared_imports';
import { schema } from './schema';
import * as i18n from './translations';
+import { isEqlRule, isThresholdRule } from '../../../../../common/detection_engine/utils';
+import { EqlQueryBar } from '../eql_query_bar';
const CommonUseField = getUseField({ component: Field });
@@ -123,9 +125,10 @@ const StepDefineRuleComponent: FC = ({
}) as unknown) as [Partial];
const index = formIndex || initialState.index;
const ruleType = formRuleType || initialState.ruleType;
- const [
- { browserFields, indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar },
- ] = useFetchIndexPatterns(index, RuleStep.defineRule);
+ const [{ browserFields, indexPatterns, isLoading: indexPatternsLoading }] = useFetchIndexPatterns(
+ index,
+ RuleStep.defineRule
+ );
// reset form when rule type changes
useEffect(() => {
@@ -186,7 +189,7 @@ const StepDefineRuleComponent: FC = ({
@@ -227,31 +230,46 @@ const StepDefineRuleComponent: FC = ({
},
}}
/>
-
- {i18n.IMPORT_TIMELINE_QUERY}
-
- ),
- }}
- component={QueryBarDefineRule}
- componentProps={{
- browserFields,
- idAria: 'detectionEngineStepDefineRuleQueryBar',
- indexPattern: indexPatternQueryBar,
- isDisabled: isLoading,
- isLoading: indexPatternLoadingQueryBar,
- dataTestSubj: 'detectionEngineStepDefineRuleQueryBar',
- openTimelineSearch,
- onCloseTimelineSearch: handleCloseTimelineSearch,
- }}
- />
+ {isEqlRule(ruleType) ? (
+
+ ) : (
+
+ {i18n.IMPORT_TIMELINE_QUERY}
+
+ ),
+ }}
+ component={QueryBarDefineRule}
+ componentProps={{
+ browserFields,
+ idAria: 'detectionEngineStepDefineRuleQueryBar',
+ indexPattern: indexPatterns,
+ isDisabled: isLoading,
+ isLoading: indexPatternsLoading,
+ dataTestSubj: 'detectionEngineStepDefineRuleQueryBar',
+ openTimelineSearch,
+ onCloseTimelineSearch: handleCloseTimelineSearch,
+ }}
+ />
+ )}
>
@@ -273,7 +291,7 @@ const StepDefineRuleComponent: FC = ({
>
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx
index 71cfbbf552d84..740cd63496682 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/batch_actions.tsx
@@ -17,6 +17,7 @@ import {
import { ActionToaster, displayWarningToast } from '../../../../../common/components/toasters';
import { Rule } from '../../../../containers/detection_engine/rules';
import * as detectionI18n from '../../translations';
+import { isMlRule } from '../../../../../../common/machine_learning/helpers';
interface GetBatchItems {
closePopover: () => void;
@@ -62,7 +63,7 @@ export const getBatchItems = ({
);
const deactivatedIdsNoML = deactivatedIds.filter(
- (id) => rules.find((r) => r.id === id)?.type !== 'machine_learning' ?? false
+ (id) => !isMlRule(rules.find((r) => r.id === id)?.type)
);
const mlRuleCount = deactivatedIds.length - deactivatedIdsNoML.length;
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts
index 79488231b29ee..11222a0a95a80 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts
@@ -241,6 +241,32 @@ describe('helpers', () => {
expect(result).toEqual(expected);
});
+
+ test('returns query fields if type is eql', () => {
+ const mockStepData: DefineStepRule = {
+ ...mockData,
+ ruleType: 'eql',
+ queryBar: {
+ ...mockData.queryBar,
+ query: {
+ ...mockData.queryBar.query,
+ language: 'eql',
+ query: 'process where process_name == "explorer.exe"',
+ },
+ },
+ };
+ const result: DefineStepRuleJson = formatDefineStepData(mockStepData);
+
+ const expected = {
+ filters: mockStepData.queryBar.filters,
+ index: mockStepData.index,
+ language: 'eql',
+ query: 'process where process_name == "explorer.exe"',
+ type: 'eql',
+ };
+
+ expect(result).toEqual(expect.objectContaining(expected));
+ });
});
describe('formatScheduleStepData', () => {
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts
index 0137777f8f8f2..2acb3e57c5a3b 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts
@@ -9,9 +9,8 @@ import moment from 'moment';
import deepmerge from 'deepmerge';
import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../../common/constants';
+import { assertUnreachable } from '../../../../../../common/utility_types';
import { transformAlertToRuleAction } from '../../../../../../common/detection_engine/transform_actions';
-import { isMlRule } from '../../../../../../common/machine_learning/helpers';
-import { isThresholdRule } from '../../../../../../common/detection_engine/utils';
import { List } from '../../../../../../common/detection_engine/schemas/types';
import { ENDPOINT_LIST_ID, ExceptionListType, NamespaceType } from '../../../../../shared_imports';
import { Rule } from '../../../../containers/detection_engine/rules';
@@ -88,16 +87,25 @@ const isThresholdFields = (
): fields is ThresholdRuleFields => has('threshold', fields);
export const filterRuleFieldsForType = (fields: T, type: Type) => {
- if (isMlRule(type)) {
- const { index, queryBar, threshold, ...mlRuleFields } = fields;
- return mlRuleFields;
- } else if (isThresholdRule(type)) {
- const { anomalyThreshold, machineLearningJobId, ...thresholdRuleFields } = fields;
- return thresholdRuleFields;
- } else {
- const { anomalyThreshold, machineLearningJobId, threshold, ...queryRuleFields } = fields;
- return queryRuleFields;
+ switch (type) {
+ case 'machine_learning':
+ const { index, queryBar, threshold, ...mlRuleFields } = fields;
+ return mlRuleFields;
+ case 'threshold':
+ const { anomalyThreshold, machineLearningJobId, ...thresholdRuleFields } = fields;
+ return thresholdRuleFields;
+ case 'query':
+ case 'saved_query':
+ case 'eql':
+ const {
+ anomalyThreshold: _a,
+ machineLearningJobId: _m,
+ threshold: _t,
+ ...queryRuleFields
+ } = fields;
+ return queryRuleFields;
}
+ assertUnreachable(type);
};
export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStepRuleJson => {
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
index 2988e031c4dd6..68799f46eee57 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
@@ -390,7 +390,7 @@ export const RuleDetailsPageComponent: FC = ({
{
const queryRuleParams = ['index', 'filters', 'language', 'query', 'saved_id'];
- if (isMlRule(ruleType)) {
- return ['anomaly_threshold', 'machine_learning_job_id'];
+ switch (ruleType) {
+ case 'machine_learning':
+ return ['anomaly_threshold', 'machine_learning_job_id'];
+ case 'threshold':
+ return ['threshold', ...queryRuleParams];
+ case 'query':
+ case 'saved_query':
+ case 'eql':
+ return queryRuleParams;
}
-
- if (ruleType === 'threshold') {
- return ['threshold', ...queryRuleParams];
- }
-
- return queryRuleParams;
+ assertUnreachable(ruleType);
};
export const getActionMessageRuleParams = (ruleType: Type): string[] => {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts
index acd800e54040c..959bf3186f136 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts
@@ -27,6 +27,7 @@ import { buildRouteValidation } from '../../../../utils/build_validation/route_v
import { transformBulkError, createBulkErrorObject, buildSiemResponse } from '../utils';
import { updateRulesNotifications } from '../../rules/update_rules_notifications';
import { PartialFilter } from '../../types';
+import { isMlRule } from '../../../../../common/machine_learning/helpers';
export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => {
router.post(
@@ -112,13 +113,10 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) =>
});
}
- const query =
- type !== 'machine_learning' && queryOrUndefined == null ? '' : queryOrUndefined;
+ const query = !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined;
const language =
- type !== 'machine_learning' && languageOrUndefined == null
- ? 'kuery'
- : languageOrUndefined;
+ !isMlRule(type) && languageOrUndefined == null ? 'kuery' : languageOrUndefined;
// TODO: Fix these either with an is conversion or by better typing them within io-ts
const actions: RuleAlertAction[] = actionsRest as RuleAlertAction[];
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts
index 482edb9925557..701e5b5e706ed 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts
@@ -24,6 +24,7 @@ import { transformError, buildSiemResponse } from '../utils';
import { updateRulesNotifications } from '../../rules/update_rules_notifications';
import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client';
import { PartialFilter } from '../../types';
+import { isMlRule } from '../../../../../common/machine_learning/helpers';
export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void => {
router.post(
@@ -86,13 +87,10 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void
exceptions_list: exceptionsList,
} = request.body;
try {
- const query =
- type !== 'machine_learning' && queryOrUndefined == null ? '' : queryOrUndefined;
+ const query = !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined;
const language =
- type !== 'machine_learning' && languageOrUndefined == null
- ? 'kuery'
- : languageOrUndefined;
+ !isMlRule(type) && languageOrUndefined == null ? 'kuery' : languageOrUndefined;
// TODO: Fix these either with an is conversion or by better typing them within io-ts
const actions: RuleAlertAction[] = actionsRest as RuleAlertAction[];
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts
index 8a7215e5a5bad..60bb8c79243d7 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts
@@ -19,6 +19,7 @@ import {
ImportRulesSchema as ImportRulesResponseSchema,
importRulesSchema as importRulesResponseSchema,
} from '../../../../../common/detection_engine/schemas/response/import_rules_schema';
+import { isMlRule } from '../../../../../common/machine_learning/helpers';
import { IRouter } from '../../../../../../../../src/core/server';
import { createPromiseFromStreams } from '../../../../../../../../src/core/server/utils/';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
@@ -174,13 +175,10 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP
} = parsedRule;
try {
- const query =
- type !== 'machine_learning' && queryOrUndefined == null ? '' : queryOrUndefined;
+ const query = !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined;
const language =
- type !== 'machine_learning' && languageOrUndefined == null
- ? 'kuery'
- : languageOrUndefined;
+ !isMlRule(type) && languageOrUndefined == null ? 'kuery' : languageOrUndefined;
// TODO: Fix these either with an is conversion or by better typing them within io-ts
const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[];
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts
index 518024387fed3..0e414e130849a 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts
@@ -13,6 +13,7 @@ import {
UpdateRulesBulkSchemaDecoded,
} from '../../../../../common/detection_engine/schemas/request/update_rules_bulk_schema';
import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema';
+import { isMlRule } from '../../../../../common/machine_learning/helpers';
import { IRouter } from '../../../../../../../../src/core/server';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
import { SetupPlugins } from '../../../../plugin';
@@ -108,13 +109,10 @@ export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) =>
});
}
- const query =
- type !== 'machine_learning' && queryOrUndefined == null ? '' : queryOrUndefined;
+ const query = !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined;
const language =
- type !== 'machine_learning' && languageOrUndefined == null
- ? 'kuery'
- : languageOrUndefined;
+ !isMlRule(type) && languageOrUndefined == null ? 'kuery' : languageOrUndefined;
// TODO: Fix these either with an is conversion or by better typing them within io-ts
const actions: RuleAlertAction[] = actionsRest as RuleAlertAction[];
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts
index 299b99c4d37b0..553d084b62633 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts
@@ -10,6 +10,7 @@ import {
updateRulesSchema,
UpdateRulesSchemaDecoded,
} from '../../../../../common/detection_engine/schemas/request/update_rules_schema';
+import { isMlRule } from '../../../../../common/machine_learning/helpers';
import { IRouter } from '../../../../../../../../src/core/server';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
import { SetupPlugins } from '../../../../plugin';
@@ -87,13 +88,10 @@ export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => {
exceptions_list: exceptionsList,
} = request.body;
try {
- const query =
- type !== 'machine_learning' && queryOrUndefined == null ? '' : queryOrUndefined;
+ const query = !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined;
const language =
- type !== 'machine_learning' && languageOrUndefined == null
- ? 'kuery'
- : languageOrUndefined;
+ !isMlRule(type) && languageOrUndefined == null ? 'kuery' : languageOrUndefined;
// TODO: Fix these either with an is conversion or by better typing them within io-ts
const actions: RuleAlertAction[] = actionsRest as RuleAlertAction[];
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts
index f77485f39a98d..6323938d6903b 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts
@@ -89,6 +89,7 @@ export const getFilter = async ({
};
switch (type) {
+ case 'eql':
case 'threshold': {
return savedId != null ? savedQueryFilter() : queryFilter();
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
index 7ee157beec789..2b6587300a581 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
@@ -14,6 +14,7 @@ import {
SERVER_APP_ID,
} from '../../../../common/constants';
import { isJobStarted, isMlRule } from '../../../../common/machine_learning/helpers';
+import { isThresholdRule, isEqlRule } from '../../../../common/detection_engine/utils';
import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates';
import { SetupPlugins } from '../../../plugin';
import { getInputIndex } from './get_input_output_index';
@@ -244,7 +245,9 @@ export const signalRulesAlertType = ({
if (bulkCreateDuration) {
result.bulkCreateTimes.push(bulkCreateDuration);
}
- } else if (type === 'threshold' && threshold) {
+ } else if (isEqlRule(type)) {
+ throw new Error('EQL Rules are under development, execution is not yet implemented');
+ } else if (isThresholdRule(type) && threshold) {
const inputIndex = await getInputIndex(services, version, index);
const esFilter = await getFilter({
type,