diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts
index 7314277c440f2..86bc1d4682854 100644
--- a/x-pack/plugins/security_solution/common/experimental_features.ts
+++ b/x-pack/plugins/security_solution/common/experimental_features.ts
@@ -89,15 +89,6 @@ export const allowedExperimentalValues = Object.freeze({
**/
newUserDetailsFlyout: false,
- /**
- * Enables Protections/Detections Coverage Overview page (Epic link https://github.com/elastic/security-team/issues/2905)
- *
- * This flag aims to facilitate the development process as the feature may not make it to 8.10 release.
- *
- * The flag doesn't have to be documented and has to be removed after the feature is ready to release.
- */
- detectionsCoverageOverview: true,
-
/**
* Enable risk engine client and initialisation of datastream, component templates and mappings
*/
diff --git a/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_components.tsx b/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_components.tsx
index 82d4c5b5a3e27..f5595ac4a826c 100644
--- a/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_components.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_components.tsx
@@ -29,3 +29,7 @@ export const MlJobCompatibilityLink = () => (
linkText={i18n.ML_JOB_COMPATIBILITY_LINK_TEXT}
/>
);
+
+export const CoverageOverviewLink = () => (
+
+);
diff --git a/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_translations.ts b/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_translations.ts
index a495d95b3cf37..fcb17b6db540f 100644
--- a/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_translations.ts
+++ b/x-pack/plugins/security_solution/public/common/components/links_to_docs/links_translations.ts
@@ -41,3 +41,11 @@ export const ML_JOB_COMPATIBILITY_LINK_TEXT = i18n.translate(
defaultMessage: 'ML job compatibility',
}
);
+
+export const COVERAGE_OVERVIEW_LINK_PATH = 'rules-coverage.html';
+export const COVERAGE_OVERVIEW_LINK_TEXT = i18n.translate(
+ 'xpack.securitySolution.documentationLinks.coverageOverview.text',
+ {
+ defaultMessage: 'Learn more.',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.ts
index 7a775b3292991..acae46d0f3701 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/coverage_overview/build_coverage_overview_mitre_graph.ts
@@ -15,6 +15,24 @@ import type { CoverageOverviewMitreSubTechnique } from '../../model/coverage_ove
import type { CoverageOverviewMitreTactic } from '../../model/coverage_overview/mitre_tactic';
import type { CoverageOverviewMitreTechnique } from '../../model/coverage_overview/mitre_technique';
+// The order the tactic columns will appear in on the coverage overview page
+const tacticOrder = [
+ 'TA0043',
+ 'TA0042',
+ 'TA0001',
+ 'TA0002',
+ 'TA0003',
+ 'TA0004',
+ 'TA0005',
+ 'TA0006',
+ 'TA0007',
+ 'TA0008',
+ 'TA0009',
+ 'TA0011',
+ 'TA0010',
+ 'TA0040',
+];
+
export function buildCoverageOverviewMitreGraph(
tactics: MitreTactic[],
techniques: MitreTechnique[],
@@ -67,9 +85,13 @@ export function buildCoverageOverviewMitreGraph(
}
}
+ const sortedTactics = tactics.sort(
+ (a, b) => tacticOrder.indexOf(a.id) - tacticOrder.indexOf(b.id)
+ );
+
const result: CoverageOverviewMitreTactic[] = [];
- for (const tactic of tactics) {
+ for (const tactic of sortedTactics) {
result.push({
id: tactic.id,
name: tactic.name,
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts
new file mode 100644
index 0000000000000..01794b34c8ee7
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.test.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine';
+import { getTotalRuleCount } from './mitre_technique';
+import { getMockCoverageOverviewMitreTechnique } from './__mocks__';
+
+describe('getTotalRuleCount', () => {
+ it('returns count of all rules when no activity filter is present', () => {
+ const payload = getMockCoverageOverviewMitreTechnique();
+ expect(getTotalRuleCount(payload)).toEqual(2);
+ });
+
+ it('returns count of one rule type when an activity filter is present', () => {
+ const payload = getMockCoverageOverviewMitreTechnique();
+ expect(getTotalRuleCount(payload, [CoverageOverviewRuleActivity.Disabled])).toEqual(1);
+ });
+
+ it('returns count of multiple rule type when multiple activity filter is present', () => {
+ const payload = getMockCoverageOverviewMitreTechnique();
+ expect(
+ getTotalRuleCount(payload, [
+ CoverageOverviewRuleActivity.Enabled,
+ CoverageOverviewRuleActivity.Disabled,
+ ])
+ ).toEqual(2);
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts
index 35587ca59fdda..589629d643810 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/model/coverage_overview/mitre_technique.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine';
import type { CoverageOverviewMitreSubTechnique } from './mitre_subtechnique';
import type { CoverageOverviewRule } from './rule';
@@ -20,3 +21,20 @@ export interface CoverageOverviewMitreTechnique {
disabledRules: CoverageOverviewRule[];
availableRules: CoverageOverviewRule[];
}
+
+export const getTotalRuleCount = (
+ technique: CoverageOverviewMitreTechnique,
+ activity?: CoverageOverviewRuleActivity[]
+): number => {
+ if (!activity) {
+ return technique.enabledRules.length + technique.disabledRules.length;
+ }
+ let totalRuleCount = 0;
+ if (activity.includes(CoverageOverviewRuleActivity.Enabled)) {
+ totalRuleCount += technique.enabledRules.length;
+ }
+ if (activity.includes(CoverageOverviewRuleActivity.Disabled)) {
+ totalRuleCount += technique.disabledRules.length;
+ }
+ return totalRuleCount;
+};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/constants.ts
index 7c170579a217a..3bbc2f5dfbfb9 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/constants.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/constants.ts
@@ -11,8 +11,6 @@ import {
} from '../../../../../common/api/detection_engine';
import * as i18n from './translations';
-export const coverageOverviewPaletteColors = ['#00BFB326', '#00BFB34D', '#00BFB399', '#00BFB3'];
-
export const coverageOverviewPanelWidth = 160;
export const coverageOverviewLegendWidth = 380;
@@ -25,10 +23,10 @@ export const coverageOverviewFilterWidth = 300;
* A corresponding color is applied if rules count >= a specific threshold
*/
export const coverageOverviewCardColorThresholds = [
- { threshold: 10, color: coverageOverviewPaletteColors[3] },
- { threshold: 7, color: coverageOverviewPaletteColors[2] },
- { threshold: 3, color: coverageOverviewPaletteColors[1] },
- { threshold: 1, color: coverageOverviewPaletteColors[0] },
+ { threshold: 10, color: '#00BFB3' },
+ { threshold: 7, color: '#00BFB399' },
+ { threshold: 3, color: '#00BFB34D' },
+ { threshold: 1, color: '#00BFB326' },
];
export const ruleActivityFilterDefaultOptions = [
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 6762ee6b1b0e2..11ee8f0d70bbc 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
@@ -5,7 +5,8 @@
* 2.0.
*/
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
+import { CoverageOverviewLink } from '../../../../common/components/links_to_docs';
import { HeaderPage } from '../../../../common/components/header_page';
import * as i18n from './translations';
@@ -14,26 +15,27 @@ import { CoverageOverviewMitreTechniquePanelPopover } from './technique_panel_po
import { CoverageOverviewFiltersPanel } from './filters_panel';
import { useCoverageOverviewDashboardContext } from './coverage_overview_dashboard_context';
+const CoverageOverviewHeaderComponent = () => (
+
+ {i18n.CoverageOverviewDashboardInformation}
+
+ }
+ />
+);
+
+const CoverageOverviewHeader = React.memo(CoverageOverviewHeaderComponent);
+
const CoverageOverviewDashboardComponent = () => {
const {
state: { data },
} = useCoverageOverviewDashboardContext();
- const subtitle = (
-
- {i18n.CoverageOverviewDashboardInformation}{' '}
-
- {i18n.CoverageOverviewDashboardInformationLink}
-
-
- );
+
return (
<>
-
+
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts
index 738eb981f37bb..5a1aee424352a 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine';
+import type { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine';
import { getCoverageOverviewFilterMock } from '../../../../../common/api/detection_engine/rule_management/coverage_overview/coverage_overview_route.mock';
import {
getMockCoverageOverviewMitreSubTechnique,
@@ -17,7 +17,6 @@ import {
extractSelected,
getNumOfCoveredSubtechniques,
getNumOfCoveredTechniques,
- getTotalRuleCount,
populateSelected,
} from './helpers';
@@ -89,26 +88,4 @@ describe('helpers', () => {
]);
});
});
-
- describe('getTotalRuleCount', () => {
- it('returns count of all rules when no activity filter is present', () => {
- const payload = getMockCoverageOverviewMitreTechnique();
- expect(getTotalRuleCount(payload)).toEqual(2);
- });
-
- it('returns count of one rule type when an activity filter is present', () => {
- const payload = getMockCoverageOverviewMitreTechnique();
- expect(getTotalRuleCount(payload, [CoverageOverviewRuleActivity.Disabled])).toEqual(1);
- });
-
- it('returns count of multiple rule type when multiple activity filter is present', () => {
- const payload = getMockCoverageOverviewMitreTechnique();
- expect(
- getTotalRuleCount(payload, [
- CoverageOverviewRuleActivity.Enabled,
- CoverageOverviewRuleActivity.Disabled,
- ])
- ).toEqual(2);
- });
- });
});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts
index 7e8f757561a78..82d50e7b9721b 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/helpers.ts
@@ -6,8 +6,10 @@
*/
import type { EuiSelectableOption } from '@elastic/eui';
-import type { CoverageOverviewRuleSource } from '../../../../../common/api/detection_engine';
-import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine';
+import type {
+ CoverageOverviewRuleActivity,
+ CoverageOverviewRuleSource,
+} from '../../../../../common/api/detection_engine';
import type { CoverageOverviewMitreTactic } from '../../../rule_management/model/coverage_overview/mitre_tactic';
import type { CoverageOverviewMitreTechnique } from '../../../rule_management/model/coverage_overview/mitre_technique';
import { coverageOverviewCardColorThresholds } from './constants';
@@ -41,20 +43,3 @@ export const populateSelected = (
allOptions.map((option) =>
selected.includes(option.label) ? { ...option, checked: 'on' } : option
);
-
-export const getTotalRuleCount = (
- technique: CoverageOverviewMitreTechnique,
- activity?: CoverageOverviewRuleActivity[]
-): number => {
- if (!activity) {
- return technique.enabledRules.length + technique.disabledRules.length;
- }
- let totalRuleCount = 0;
- if (activity.includes(CoverageOverviewRuleActivity.Enabled)) {
- totalRuleCount += technique.enabledRules.length;
- }
- if (activity.includes(CoverageOverviewRuleActivity.Disabled)) {
- totalRuleCount += technique.disabledRules.length;
- }
- return totalRuleCount;
-};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel.tsx
index e182b6445513c..821f0f18dcd72 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel.tsx
@@ -9,9 +9,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
import { css } from '@emotion/css';
import React, { memo, useCallback, useMemo } from 'react';
import type { CoverageOverviewMitreTechnique } from '../../../rule_management/model/coverage_overview/mitre_technique';
+import { getTotalRuleCount } from '../../../rule_management/model/coverage_overview/mitre_technique';
import { coverageOverviewPanelWidth } from './constants';
import { useCoverageOverviewDashboardContext } from './coverage_overview_dashboard_context';
-import { getCardBackgroundColor, getTotalRuleCount } from './helpers';
+import { getCardBackgroundColor } from './helpers';
import { CoverageOverviewPanelRuleStats } from './shared_components/panel_rule_stats';
import * as i18n from './translations';
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/translations.ts
index c3e205531fdce..eb9f06c350421 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/translations.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/translations.ts
@@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
export const COVERAGE_OVERVIEW_DASHBOARD_TITLE = i18n.translate(
'xpack.securitySolution.coverageOverviewDashboard.pageTitle',
{
- defaultMessage: 'MITRE ATT&CK\u00AE Coverage',
+ defaultMessage: 'MITRE ATT&CK\u00AE coverage',
}
);
@@ -174,13 +174,6 @@ export const CoverageOverviewDashboardInformation = i18n.translate(
'xpack.securitySolution.coverageOverviewDashboard.dashboardInformation',
{
defaultMessage:
- 'The interactive MITRE ATT&CK coverage below shows the current state of your coverage from installed rules, click on a cell to view further details. Unmapped rules will not be displayed. View further information from our',
- }
-);
-
-export const CoverageOverviewDashboardInformationLink = i18n.translate(
- 'xpack.securitySolution.coverageOverviewDashboard.dashboardInformationLink',
- {
- defaultMessage: 'docs.',
+ "Your current coverage of MITRE ATT&CK\u00AE tactics and techniques, based on installed rules. Click a cell to view and enable a technique's rules. Rules must be mapped to the MITRE ATT&CK\u00AE framework to be displayed.",
}
);
diff --git a/x-pack/plugins/security_solution/public/rules/links.ts b/x-pack/plugins/security_solution/public/rules/links.ts
index d5ae12b4b1ff0..14d4c955e4a3b 100644
--- a/x-pack/plugins/security_solution/public/rules/links.ts
+++ b/x-pack/plugins/security_solution/public/rules/links.ts
@@ -103,7 +103,6 @@ export const links: LinkItem = {
defaultMessage: 'MITRE ATT&CK Coverage',
}),
],
- experimentalKey: 'detectionsCoverageOverview',
},
],
categories: [
diff --git a/x-pack/plugins/security_solution/public/rules/routes.tsx b/x-pack/plugins/security_solution/public/rules/routes.tsx
index aefdd505c95e0..182a69c39f19b 100644
--- a/x-pack/plugins/security_solution/public/rules/routes.tsx
+++ b/x-pack/plugins/security_solution/public/rules/routes.tsx
@@ -31,7 +31,6 @@ import { AllRulesTabs } from '../detection_engine/rule_management_ui/components/
import { AddRulesPage } from '../detection_engine/rule_management_ui/pages/add_rules';
import type { SecuritySubPluginRoutes } from '../app/types';
import { RulesLandingPage } from './landing';
-import { useIsExperimentalFeatureEnabled } from '../common/hooks/use_experimental_features';
import { CoverageOverviewPage } from '../detection_engine/rule_management_ui/pages/coverage_overview';
const RulesSubRoutes = [
@@ -109,21 +108,13 @@ const RulesContainerComponent: React.FC = () => {
const Rules = React.memo(RulesContainerComponent);
-const CoverageOverviewRoutes = () => {
- const isDetectionsCoverageOverviewEnabled = useIsExperimentalFeatureEnabled(
- 'detectionsCoverageOverview'
- );
-
- return isDetectionsCoverageOverviewEnabled ? (
-
-
-
-
-
- ) : (
-
- );
-};
+const CoverageOverviewRoutes = () => (
+
+
+
+
+
+);
export const routes: SecuritySubPluginRoutes = [
{
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts
index f7c865605dc3d..ed0f19c7015b6 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts
@@ -62,8 +62,6 @@ export const registerRuleManagementRoutes = (
// Rules filters
getRuleManagementFilters(router);
- // Rules dashboard
- if (config.experimentalFeatures.detectionsCoverageOverview) {
- getCoverageOverviewRoute(router);
- }
+ // Rules coverage overview
+ getCoverageOverviewRoute(router);
};
diff --git a/x-pack/test/detection_engine_api_integration/common/config.ts b/x-pack/test/detection_engine_api_integration/common/config.ts
index 7f3598ee8d5e8..c4c3c44f1c418 100644
--- a/x-pack/test/detection_engine_api_integration/common/config.ts
+++ b/x-pack/test/detection_engine_api_integration/common/config.ts
@@ -78,7 +78,6 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s
'previewTelemetryUrlEnabled',
'riskScoringPersistence',
'riskScoringRoutesEnabled',
- 'detectionsCoverageOverview',
])}`,
'--xpack.task_manager.poll_interval=1000',
`--xpack.actions.preconfigured=${JSON.stringify({