diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx index 5ee4bb4bb1f47..113f81631a06a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx @@ -14,6 +14,7 @@ import { buildListItems, getDescriptionItem, } from '.'; +import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { FilterManager, UI_SETTINGS } from '@kbn/data-plugin/public'; import type { Filter } from '@kbn/es-query'; @@ -534,5 +535,207 @@ describe('description_step', () => { expect(React.isValidElement(result[0].description)).toBeTruthy(); }); }); + + describe('alert suppression', () => { + const ruleTypesWithoutSuppression: Type[] = [ + 'eql', + 'esql', + 'machine_learning', + 'new_terms', + 'threat_match', + ]; + const suppressionFields = { + groupByDuration: { + unit: 'm', + value: 50, + }, + groupByRadioSelection: 'per-time-period', + enableThresholdSuppression: true, + groupByFields: ['agent.name'], + suppressionMissingFields: 'suppress', + }; + describe('groupByDuration', () => { + ruleTypesWithoutSuppression.forEach((ruleType) => { + test(`should be empty if rule is ${ruleType}`, () => { + const result: ListItems[] = getDescriptionItem( + 'groupByDuration', + 'label', + { + ruleType, + ...suppressionFields, + }, + mockFilterManager, + mockLicenseService + ); + + expect(result).toEqual([]); + }); + }); + + ['query', 'saved_query'].forEach((ruleType) => { + test(`should be empty if groupByFields empty for ${ruleType} rule`, () => { + const result: ListItems[] = getDescriptionItem( + 'groupByDuration', + 'label', + { + ruleType: 'query', + ...suppressionFields, + groupByFields: [], + }, + mockFilterManager, + mockLicenseService + ); + + expect(result).toEqual([]); + }); + + test(`should return item for ${ruleType} rule`, () => { + const result: ListItems[] = getDescriptionItem( + 'groupByDuration', + 'label', + { + ruleType: 'query', + ...suppressionFields, + }, + mockFilterManager, + mockLicenseService + ); + + expect(result[0].description).toBe('50m'); + }); + }); + + test('should return item for threshold rule', () => { + const result: ListItems[] = getDescriptionItem( + 'groupByDuration', + 'label', + { + ruleType: 'threshold', + ...suppressionFields, + }, + mockFilterManager, + mockLicenseService + ); + + expect(result[0].description).toBe('50m'); + }); + + test('should return item for threshold rule if groupByFields empty', () => { + const result: ListItems[] = getDescriptionItem( + 'groupByDuration', + 'label', + { + ruleType: 'threshold', + ...suppressionFields, + groupByFields: [], + }, + mockFilterManager, + mockLicenseService + ); + + expect(result[0].description).toBe('50m'); + }); + + test('should be empty for threshold rule if suppression not enabled', () => { + const result: ListItems[] = getDescriptionItem( + 'groupByDuration', + 'label', + { + ruleType: 'threshold', + ...suppressionFields, + enableThresholdSuppression: false, + }, + mockFilterManager, + mockLicenseService + ); + + expect(result).toEqual([]); + }); + }); + + describe('groupByFields', () => { + [...ruleTypesWithoutSuppression, 'threshold'].forEach((ruleType) => { + test(`should be empty if rule is ${ruleType}`, () => { + const result: ListItems[] = getDescriptionItem( + 'groupByFields', + 'label', + { + ruleType, + ...suppressionFields, + }, + mockFilterManager, + mockLicenseService + ); + + expect(result).toEqual([]); + }); + }); + ['query', 'saved_query'].forEach((ruleType) => { + test(`should return item for ${ruleType} rule`, () => { + const result: ListItems[] = getDescriptionItem( + 'groupByFields', + 'label', + { + ruleType, + ...suppressionFields, + }, + mockFilterManager, + mockLicenseService + ); + expect(mount(result[0].description as React.ReactElement).text()).toBe('agent.name'); + }); + }); + }); + + describe('suppressionMissingFields', () => { + [...ruleTypesWithoutSuppression, 'threshold'].forEach((ruleType) => { + test(`should be empty if rule is ${ruleType}`, () => { + const result: ListItems[] = getDescriptionItem( + 'suppressionMissingFields', + 'label', + { + ruleType, + ...suppressionFields, + }, + mockFilterManager, + mockLicenseService + ); + + expect(result).toEqual([]); + }); + }); + ['query', 'saved_query'].forEach((ruleType) => { + test(`should return item for ${ruleType} rule`, () => { + const result: ListItems[] = getDescriptionItem( + 'suppressionMissingFields', + 'label', + { + ruleType, + ...suppressionFields, + }, + mockFilterManager, + mockLicenseService + ); + expect(result[0].description).toContain('Suppress'); + }); + + test(`should be empty if groupByFields empty for ${ruleType} rule`, () => { + const result: ListItems[] = getDescriptionItem( + 'suppressionMissingFields', + 'label', + { + ruleType: 'query', + ...suppressionFields, + groupByFields: [], + }, + mockFilterManager, + mockLicenseService + ); + + expect(result).toEqual([]); + }); + }); + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index 7dc9ca3bd41f4..883c01430e644 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -56,6 +56,7 @@ import { THREAT_QUERY_LABEL } from './translations'; import { filterEmptyThreats } from '../../../../detection_engine/rule_creation_ui/pages/rule_creation/helpers'; import { useLicense } from '../../../../common/hooks/use_license'; import type { LicenseService } from '../../../../../common/license'; +import { isThresholdRule, isQueryRule } from '../../../../../common/detection_engine/utils'; const DescriptionListContainer = styled(EuiDescriptionList)` max-width: 600px; @@ -204,12 +205,29 @@ export const getDescriptionItem = ( } else if (field === 'responseActions') { return []; } else if (field === 'groupByFields') { + const ruleType: Type = get('ruleType', data); + const ruleCanHaveGroupByFields = isQueryRule(ruleType); + if (!ruleCanHaveGroupByFields) { + return []; + } const values: string[] = get(field, data); return buildAlertSuppressionDescription(label, values); } else if (field === 'groupByRadioSelection') { return []; } else if (field === 'groupByDuration') { - if (get('groupByFields', data).length > 0) { + const ruleType: Type = get('ruleType', data); + const ruleCanHaveDuration = isQueryRule(ruleType) || isThresholdRule(ruleType); + if (!ruleCanHaveDuration) { + return []; + } + + // threshold rule has suppression duration without grouping fields, but suppression should be explicitly enabled by user + // query rule have suppression duration only if group by fields selected + const showDuration = isThresholdRule(ruleType) + ? get('enableThresholdSuppression', data) === true + : get('groupByFields', data).length > 0; + + if (showDuration) { const value: Duration = get(field, data); return buildAlertSuppressionWindowDescription( label, @@ -220,6 +238,11 @@ export const getDescriptionItem = ( return []; } } else if (field === 'suppressionMissingFields') { + const ruleType: Type = get('ruleType', data); + const ruleCanHaveSuppressionMissingFields = isQueryRule(ruleType); + if (!ruleCanHaveSuppressionMissingFields) { + return []; + } if (get('groupByFields', data).length > 0) { const value = get(field, data); return buildAlertSuppressionMissingFieldsDescription(label, value); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule.cy.ts index 074670221bedd..fa925131892e1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule.cy.ts @@ -163,6 +163,10 @@ describe( enablesAndPopulatesThresholdSuppression(5, 'h'); fillDefineThresholdRuleAndContinue(rule); + // ensures duration displayed on define step in preview mode + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '5h'); + }); fillAboutRuleMinimumAndContinue(rule); skipScheduleRuleAction();