diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard.tsx
index 11ee8f0d70bbc..fb14abd42ee61 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard.tsx
@@ -40,7 +40,12 @@ const CoverageOverviewDashboardComponent = () => {
{data?.mitreTactics.map((tactic) => (
-
+
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts
index 8a935e203fe83..6a52134d4d738 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/coverage_overview/coverage_overview.cy.ts
@@ -17,7 +17,6 @@ import {
COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS,
COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS,
COVERAGE_OVERVIEW_TACTIC_PANEL,
- COVERAGE_OVERVIEW_TECHNIQUE_TITLE,
} from '../../../../screens/rules_coverage_overview';
import { createRule } from '../../../../tasks/api_calls/rules';
import { visit } from '../../../../tasks/navigation';
@@ -37,6 +36,7 @@ import {
enableAllDisabledRules,
filterCoverageOverviewBySearchBar,
openTechniquePanelByName,
+ openTechniquePanelByNameAndTacticId,
selectCoverageOverviewActivityFilterOption,
selectCoverageOverviewSourceFilterOption,
} from '../../../../tasks/rules_coverage_overview';
@@ -48,8 +48,8 @@ const EnabledCustomRuleMitreData = getMockThreatData()[2];
const DisabledCustomRuleMitreData = getMockThreatData()[3];
// Mitre data used for duplicate technique tests
-const DuplicateTechniqueMitreData1 = getDuplicateTechniqueThreatData()[0];
-const DuplicateTechniqueMitreData2 = getDuplicateTechniqueThreatData()[1];
+const DuplicateTechniqueMitreData1 = getDuplicateTechniqueThreatData()[1];
+const DuplicateTechniqueMitreData2 = getDuplicateTechniqueThreatData()[0];
const MockEnabledPrebuiltRuleThreat: Threat = {
framework: 'MITRE ATT&CK',
@@ -233,7 +233,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => {
describe('filtering tests', () => {
it('filters for all data', () => {
- selectCoverageOverviewActivityFilterOption('Disabled rules');
+ selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load
openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name);
cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Enabled prebuilt rule');
@@ -250,7 +250,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => {
it('filters for disabled and prebuilt rules', () => {
selectCoverageOverviewActivityFilterOption('Enabled rules'); // Disables default filter
- selectCoverageOverviewActivityFilterOption('Disabled rules');
+ selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load
selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter
openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name);
@@ -273,7 +273,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => {
});
it('filters for only prebuilt rules', () => {
- selectCoverageOverviewActivityFilterOption('Disabled rules');
+ selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load
selectCoverageOverviewSourceFilterOption('Custom rules'); // Disables default filter
openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name);
@@ -294,7 +294,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => {
});
it('filters for only custom rules', () => {
- selectCoverageOverviewActivityFilterOption('Disabled rules');
+ selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load
selectCoverageOverviewSourceFilterOption('Elastic rules'); // Disables default filter
openTechniquePanelByName(EnabledPrebuiltRuleMitreData.technique.name);
@@ -338,7 +338,7 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => {
});
it('enables all disabled rules', () => {
- selectCoverageOverviewActivityFilterOption('Disabled rules');
+ selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load
openTechniquePanelByName(DisabledPrebuiltRuleMitreData.technique.name);
enableAllDisabledRules();
@@ -355,6 +355,10 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => {
});
describe('with rules that have identical mitre techniques that belong to multiple tactics', () => {
+ const SharedTechniqueName = DuplicateTechniqueMitreData1.technique.name;
+ const TacticOfRule1 = DuplicateTechniqueMitreData1.tactic;
+ const TacticOfRule2 = DuplicateTechniqueMitreData2.tactic;
+
beforeEach(() => {
login();
deleteAlertsAndRules();
@@ -363,14 +367,14 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => {
getNewRule({
rule_id: 'duplicate_technique_rule_1',
enabled: true,
- name: 'Rule with tactic 1',
+ name: 'Rule under Persistence tactic',
threat: [MockCustomRuleDuplicateTechniqueThreat1],
})
);
createRule(
getNewRule({
rule_id: 'duplicate_technique_rule_2',
- name: 'Rule with tactic 2',
+ name: 'Rule under Privilege Escalation tactic',
enabled: false,
threat: [MockCustomRuleDuplicateTechniqueThreat2],
})
@@ -379,49 +383,55 @@ describe('Coverage overview', { tags: ['@ess', '@serverless'] }, () => {
});
it('technique panels render unique rule data', () => {
- selectCoverageOverviewActivityFilterOption('Disabled rules');
+ // Tests to make sure each rule only exists in the specific technique and tactic that's assigned to it
- // Open duplicated technique panel under first tactic
- cy.get(COVERAGE_OVERVIEW_TECHNIQUE_TITLE(DuplicateTechniqueMitreData1.technique.id))
- .first()
- .click();
- cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES)
- .contains('Rule with tactic 1')
- .should('not.exist');
- cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains('Rule with tactic 2');
+ selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load
- // Open duplicated technique panel under second tactic
- cy.get(COVERAGE_OVERVIEW_TECHNIQUE_TITLE(DuplicateTechniqueMitreData2.technique.id))
- .last()
- .click();
- cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Rule with tactic 1');
+ // Open duplicated technique panel under Persistence tactic
+ openTechniquePanelByNameAndTacticId(SharedTechniqueName, TacticOfRule1.id);
+
+ // Only rule 1 data is present
+ cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES).contains('Rule under Persistence tactic');
cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES)
- .contains('Rule with tactic 2')
+ .contains('Rule under Privilege Escalation tactic')
.should('not.exist');
+
+ // Open duplicated technique panel under Privilege Escalation tactic
+ openTechniquePanelByNameAndTacticId(SharedTechniqueName, TacticOfRule2.id);
+
+ // Only rule 2 data is present
+ cy.get(COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES)
+ .contains('Rule under Persistence tactic')
+ .should('not.exist');
+ cy.get(COVERAGE_OVERVIEW_POPOVER_DISABLED_RULES).contains(
+ 'Rule under Privilege Escalation tactic'
+ );
});
it('tactic panels render correct rule stats', () => {
- selectCoverageOverviewActivityFilterOption('Disabled rules');
+ selectCoverageOverviewActivityFilterOption('Disabled rules'); // Activates disabled rules filter as it's off by default on page load
- // Validate rule count stats for first tactic
+ // Validate rule count stats for the Persistence tactic only show stats based on its own technique
+ // Enabled rule count
cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL)
- .contains(DuplicateTechniqueMitreData1.tactic.name)
+ .contains(TacticOfRule1.name)
.get(COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS)
.contains('0');
-
+ // Disabled rule count
cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL)
- .contains(DuplicateTechniqueMitreData1.tactic.name)
+ .contains(TacticOfRule1.name)
.get(COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS)
.contains('1');
- // Validate rule count stats for second tactic
+ // Validate rule count stats for the Privilege Escalation tactic only show stats based on its own technique
+ // Enabled rule count
cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL)
- .contains(DuplicateTechniqueMitreData2.tactic.name)
+ .contains(TacticOfRule2.name)
.get(COVERAGE_OVERVIEW_TACTIC_ENABLED_STATS)
.contains('1');
-
+ // Disabled rule count
cy.get(COVERAGE_OVERVIEW_TACTIC_PANEL)
- .contains(DuplicateTechniqueMitreData2.tactic.name)
+ .contains(TacticOfRule2.name)
.get(COVERAGE_OVERVIEW_TACTIC_DISABLED_STATS)
.contains('0');
});
diff --git a/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts
index 2bcfa86cf0f9f..bf696867e61cf 100644
--- a/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts
+++ b/x-pack/test/security_solution_cypress/cypress/screens/rules_coverage_overview.ts
@@ -8,8 +8,8 @@
export const COVERAGE_OVERVIEW_TECHNIQUE_PANEL =
'[data-test-subj="coverageOverviewTechniquePanel"]';
-export const COVERAGE_OVERVIEW_TECHNIQUE_TITLE = (id: string) =>
- `[data-test-subj="coverageOverviewTechniqueTitle-${id}"]`;
+export const COVERAGE_OVERVIEW_TECHNIQUE_PANEL_IN_TACTIC_GROUP = (id: string) =>
+ `[data-test-subj="coverageOverviewTacticGroup-${id}"] [data-test-subj="coverageOverviewTechniquePanel"]`;
export const COVERAGE_OVERVIEW_POPOVER_ENABLED_RULES =
'[data-test-subj="coverageOverviewEnabledRulesList"]';
diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts b/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts
index 3f9b9f472940f..5f8f18bb8a36b 100644
--- a/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts
+++ b/x-pack/test/security_solution_cypress/cypress/tasks/rules_coverage_overview.ts
@@ -11,6 +11,7 @@ import {
COVERAGE_OVERVIEW_FILTER_LIST,
COVERAGE_OVERVIEW_SEARCH_BAR,
COVERAGE_OVERVIEW_SOURCE_FILTER_BUTTON,
+ COVERAGE_OVERVIEW_TECHNIQUE_PANEL_IN_TACTIC_GROUP,
COVERAGE_OVERVIEW_TECHNIQUE_PANEL,
} from '../screens/rules_coverage_overview';
import { LOADING_INDICATOR } from '../screens/security_header';
@@ -19,6 +20,10 @@ export const openTechniquePanelByName = (label: string) => {
cy.get(COVERAGE_OVERVIEW_TECHNIQUE_PANEL).contains(label).click();
};
+export const openTechniquePanelByNameAndTacticId = (label: string, tacticId: string) => {
+ cy.get(COVERAGE_OVERVIEW_TECHNIQUE_PANEL_IN_TACTIC_GROUP(tacticId)).contains(label).click();
+};
+
export const selectCoverageOverviewActivityFilterOption = (option: string) => {
cy.get(COVERAGE_OVERVIEW_ACTIVITY_FILTER_BUTTON).click(); // open filter popover
cy.get(COVERAGE_OVERVIEW_FILTER_LIST).contains(option).click();