diff --git a/x-pack/plugins/security_solution/public/dashboards/links.ts b/x-pack/plugins/security_solution/public/dashboards/links.ts
index d71ba02064de1..0f4f8800578f6 100644
--- a/x-pack/plugins/security_solution/public/dashboards/links.ts
+++ b/x-pack/plugins/security_solution/public/dashboards/links.ts
@@ -15,7 +15,6 @@ import {
detectionResponseLinks,
entityAnalyticsLinks,
overviewLinks,
- coverageOverviewDashboardLinks,
} from '../overview/links';
import { IconDashboards } from '../common/icons/dashboards';
@@ -27,7 +26,6 @@ const subLinks: LinkItem[] = [
vulnerabilityDashboardLink,
entityAnalyticsLinks,
ecsDataQualityDashboardLinks,
- coverageOverviewDashboardLinks,
].map((link) => ({ ...link, sideNavIcon: IconDashboards }));
export const dashboardsLinks: LinkItem = {
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 85c734bbc077b..7c170579a217a 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
@@ -5,14 +5,13 @@
* 2.0.
*/
-import { euiPalettePositive } from '@elastic/eui';
import {
CoverageOverviewRuleActivity,
CoverageOverviewRuleSource,
} from '../../../../../common/api/detection_engine';
import * as i18n from './translations';
-export const coverageOverviewPaletteColors = euiPalettePositive(5);
+export const coverageOverviewPaletteColors = ['#00BFB326', '#00BFB34D', '#00BFB399', '#00BFB3'];
export const coverageOverviewPanelWidth = 160;
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 b9ed3be8ad6ac..6762ee6b1b0e2 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,7 @@
* 2.0.
*/
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
import { HeaderPage } from '../../../../common/components/header_page';
import * as i18n from './translations';
@@ -18,9 +18,22 @@ 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/coverage_overview_dashboard_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx
index db96f1a5b8018..e76075535b193 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx
@@ -14,11 +14,11 @@ import React, {
useReducer,
} from 'react';
import { invariant } from '../../../../../common/utils/invariant';
-import type {
+import {
+ BulkActionType,
CoverageOverviewRuleActivity,
CoverageOverviewRuleSource,
} from '../../../../../common/api/detection_engine';
-import { BulkActionType } from '../../../../../common/api/detection_engine';
import type { CoverageOverviewDashboardState } from './coverage_overview_dashboard_reducer';
import {
SET_SHOW_EXPANDED_CELLS,
@@ -53,7 +53,10 @@ interface CoverageOverviewDashboardContextProviderProps {
export const initialState: CoverageOverviewDashboardState = {
showExpandedCells: false,
- filter: {},
+ filter: {
+ activity: [CoverageOverviewRuleActivity.Enabled],
+ source: [CoverageOverviewRuleSource.Prebuilt, CoverageOverviewRuleSource.Custom],
+ },
data: undefined,
isLoading: false,
};
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 5a1aee424352a..738eb981f37bb 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 type { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine';
+import { CoverageOverviewRuleActivity } from '../../../../../common/api/detection_engine';
import { getCoverageOverviewFilterMock } from '../../../../../common/api/detection_engine/rule_management/coverage_overview/coverage_overview_route.mock';
import {
getMockCoverageOverviewMitreSubTechnique,
@@ -17,6 +17,7 @@ import {
extractSelected,
getNumOfCoveredSubtechniques,
getNumOfCoveredTechniques,
+ getTotalRuleCount,
populateSelected,
} from './helpers';
@@ -88,4 +89,26 @@ 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 82d50e7b9721b..7e8f757561a78 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,10 +6,8 @@
*/
import type { EuiSelectableOption } from '@elastic/eui';
-import type {
- CoverageOverviewRuleActivity,
- CoverageOverviewRuleSource,
-} from '../../../../../common/api/detection_engine';
+import type { CoverageOverviewRuleSource } from '../../../../../common/api/detection_engine';
+import { CoverageOverviewRuleActivity } 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';
@@ -43,3 +41,20 @@ 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/rule_activity_filter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_activity_filter.tsx
index 0bb7e082e861e..43159822deab3 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_activity_filter.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_activity_filter.tsx
@@ -97,7 +97,6 @@ const RuleActivityFilterComponent = ({
{i18n.CoverageOverviewFilterPopoverTitle}
{i18n.CoverageOverviewFilterPopoverClearAll}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_source_filter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_source_filter.tsx
index c17af658672da..fd1995beb68d7 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_source_filter.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/rule_source_filter.tsx
@@ -96,7 +96,6 @@ const RuleSourceFilterComponent = ({
{i18n.CoverageOverviewFilterPopoverTitle}
{i18n.CoverageOverviewFilterPopoverClearAll}
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 8de089d62e298..e182b6445513c 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
@@ -10,7 +10,8 @@ import { css } from '@emotion/css';
import React, { memo, useCallback, useMemo } from 'react';
import type { CoverageOverviewMitreTechnique } from '../../../rule_management/model/coverage_overview/mitre_technique';
import { coverageOverviewPanelWidth } from './constants';
-import { getCardBackgroundColor } from './helpers';
+import { useCoverageOverviewDashboardContext } from './coverage_overview_dashboard_context';
+import { getCardBackgroundColor, getTotalRuleCount } from './helpers';
import { CoverageOverviewPanelRuleStats } from './shared_components/panel_rule_stats';
import * as i18n from './translations';
@@ -29,9 +30,13 @@ const CoverageOverviewMitreTechniquePanelComponent = ({
isPopoverOpen,
isExpanded,
}: CoverageOverviewMitreTechniquePanelProps) => {
+ const {
+ state: { filter },
+ } = useCoverageOverviewDashboardContext();
+ const totalRuleCount = getTotalRuleCount(technique, filter.activity);
const techniqueBackgroundColor = useMemo(
- () => getCardBackgroundColor(technique.enabledRules.length),
- [technique.enabledRules.length]
+ () => getCardBackgroundColor(totalRuleCount),
+ [totalRuleCount]
);
const handlePanelOnClick = useCallback(
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.test.tsx
index a41cdad7abb58..896a5c64ca9b2 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.test.tsx
@@ -13,8 +13,10 @@ import { TestProviders } from '../../../../common/mock';
import type { CoverageOverviewMitreTechnique } from '../../../rule_management/model/coverage_overview/mitre_technique';
import { CoverageOverviewMitreTechniquePanelPopover } from './technique_panel_popover';
import { useCoverageOverviewDashboardContext } from './coverage_overview_dashboard_context';
+import { useUserData } from '../../../../detections/components/user_info';
jest.mock('./coverage_overview_dashboard_context');
+jest.mock('../../../../detections/components/user_info');
const mockEnableAllDisabled = jest.fn();
@@ -31,9 +33,10 @@ const renderTechniquePanelPopover = (
describe('CoverageOverviewMitreTechniquePanelPopover', () => {
beforeEach(() => {
(useCoverageOverviewDashboardContext as jest.Mock).mockReturnValue({
- state: { showExpandedCells: false },
+ state: { showExpandedCells: false, filter: {} },
actions: { enableAllDisabled: mockEnableAllDisabled },
});
+ (useUserData as jest.Mock).mockReturnValue([{ loading: false, canUserCRUD: true }]);
});
afterEach(() => {
@@ -49,7 +52,7 @@ describe('CoverageOverviewMitreTechniquePanelPopover', () => {
test('it renders panel with expanded view', () => {
(useCoverageOverviewDashboardContext as jest.Mock).mockReturnValue({
- state: { showExpandedCells: true },
+ state: { showExpandedCells: true, filter: {} },
actions: { enableAllDisabled: mockEnableAllDisabled },
});
const wrapper = renderTechniquePanelPopover();
@@ -103,4 +106,14 @@ describe('CoverageOverviewMitreTechniquePanelPopover', () => {
});
expect(wrapper.getByTestId('enableAllDisabledButton')).toBeDisabled();
});
+
+ test('"Enable all disabled" button is disabled when user does not have CRUD permissions', async () => {
+ (useUserData as jest.Mock).mockReturnValue([{ loading: false, canUserCRUD: false }]);
+ const wrapper = renderTechniquePanelPopover();
+
+ act(() => {
+ fireEvent.click(wrapper.getByTestId('coverageOverviewTechniquePanel'));
+ });
+ expect(wrapper.getByTestId('enableAllDisabledButton')).toBeDisabled();
+ });
});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.tsx
index 9beae73a21c4c..f5fc71b08b055 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/technique_panel_popover.tsx
@@ -21,6 +21,7 @@ import {
} from '@elastic/eui';
import { css, cx } from '@emotion/css';
import React, { memo, useCallback, useMemo, useState } from 'react';
+import { useUserData } from '../../../../detections/components/user_info';
import type { CoverageOverviewMitreTechnique } from '../../../rule_management/model/coverage_overview/mitre_technique';
import { getNumOfCoveredSubtechniques } from './helpers';
import { CoverageOverviewRuleListHeader } from './shared_components/popover_list_header';
@@ -36,13 +37,19 @@ export interface CoverageOverviewMitreTechniquePanelPopoverProps {
const CoverageOverviewMitreTechniquePanelPopoverComponent = ({
technique,
}: CoverageOverviewMitreTechniquePanelPopoverProps) => {
+ const [{ loading: userInfoLoading, canUserCRUD }] = useUserData();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
- const [isEnableButtonLoading, setIsDisableButtonLoading] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
const coveredSubtechniques = useMemo(() => getNumOfCoveredSubtechniques(technique), [technique]);
const isEnableButtonDisabled = useMemo(
- () => technique.disabledRules.length === 0,
- [technique.disabledRules.length]
+ () => !canUserCRUD || technique.disabledRules.length === 0,
+ [canUserCRUD, technique.disabledRules.length]
+ );
+
+ const isEnableButtonLoading = useMemo(
+ () => isLoading || userInfoLoading,
+ [isLoading, userInfoLoading]
);
const {
@@ -51,10 +58,10 @@ const CoverageOverviewMitreTechniquePanelPopoverComponent = ({
} = useCoverageOverviewDashboardContext();
const handleEnableAllDisabled = useCallback(async () => {
- setIsDisableButtonLoading(true);
+ setIsLoading(true);
const ruleIds = technique.disabledRules.map((rule) => rule.id);
await enableAllDisabled(ruleIds);
- setIsDisableButtonLoading(false);
+ setIsLoading(false);
closePopover();
}, [closePopover, enableAllDisabled, technique.disabledRules]);
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 b4aa93f2bcc02..c3e205531fdce 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
@@ -152,7 +152,7 @@ export const CoverageOverviewSearchBarPlaceholder = i18n.translate(
'xpack.securitySolution.coverageOverviewDashboard.searchBarPlaceholder',
{
defaultMessage:
- 'Search for the tactic, technique (e.g.,"defence evasion" or "TA0005") or rule name, index pattern (e.g.,"filebeat-*")',
+ 'Search for the tactic, technique (e.g.,"defense evasion" or "TA0005") or rule name',
}
);
@@ -169,3 +169,18 @@ export const CoverageOverviewFilterPopoverClearAll = i18n.translate(
defaultMessage: 'Clear all',
}
);
+
+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.',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/overview/links.ts b/x-pack/plugins/security_solution/public/overview/links.ts
index a03447adbc732..069adc35a6f5c 100644
--- a/x-pack/plugins/security_solution/public/overview/links.ts
+++ b/x-pack/plugins/security_solution/public/overview/links.ts
@@ -7,7 +7,6 @@
import { i18n } from '@kbn/i18n';
import {
- COVERAGE_OVERVIEW_PATH,
DATA_QUALITY_PATH,
DETECTION_RESPONSE_PATH,
ENTITY_ANALYTICS_PATH,
@@ -22,7 +21,6 @@ import {
GETTING_STARTED,
OVERVIEW,
ENTITY_ANALYTICS,
- COVERAGE_OVERVIEW,
} from '../app/translations';
import type { LinkItem } from '../common/links/types';
import overviewPageImg from '../common/images/overview_page.png';
@@ -113,24 +111,3 @@ export const ecsDataQualityDashboardLinks: LinkItem = {
}),
],
};
-
-export const coverageOverviewDashboardLinks: LinkItem = {
- id: SecurityPageName.coverageOverview,
- title: COVERAGE_OVERVIEW,
- landingImage: overviewPageImg, // TODO: change with updated image before removing feature flag https://github.com/elastic/security-team/issues/2905
- description: i18n.translate(
- 'xpack.securitySolution.appLinks.coverageOverviewDashboardDescription',
- {
- defaultMessage:
- 'An overview of rule coverage according to the MITRE ATT&CK\u00AE specifications',
- }
- ),
- path: COVERAGE_OVERVIEW_PATH,
- capabilities: [`${SERVER_APP_ID}.show`],
- globalSearchKeywords: [
- i18n.translate('xpack.securitySolution.appLinks.coverageOverviewDashboard', {
- defaultMessage: 'MITRE ATT&CK Coverage',
- }),
- ],
- experimentalKey: 'detectionsCoverageOverview',
-};
diff --git a/x-pack/plugins/security_solution/public/overview/routes.tsx b/x-pack/plugins/security_solution/public/overview/routes.tsx
index 0d9dd4100ef34..ab986b47c30f7 100644
--- a/x-pack/plugins/security_solution/public/overview/routes.tsx
+++ b/x-pack/plugins/security_solution/public/overview/routes.tsx
@@ -7,7 +7,6 @@
import React from 'react';
import { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
-import { Redirect } from 'react-router-dom';
import {
LANDING_PATH,
OVERVIEW_PATH,
@@ -15,7 +14,6 @@ import {
DETECTION_RESPONSE_PATH,
SecurityPageName,
ENTITY_ANALYTICS_PATH,
- COVERAGE_OVERVIEW_PATH,
} from '../../common/constants';
import type { SecuritySubPluginRoutes } from '../app/types';
@@ -25,9 +23,7 @@ import { DetectionResponse } from './pages/detection_response';
import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper';
import { EntityAnalyticsPage } from './pages/entity_analytics';
import { SecurityRoutePageWrapper } from '../common/components/security_route_page_wrapper';
-import { CoverageOverviewPage } from '../detection_engine/rule_management_ui/pages/coverage_overview';
import { LandingPage } from './pages/landing';
-import { useIsExperimentalFeatureEnabled } from '../common/hooks/use_experimental_features';
const OverviewRoutes = () => (
@@ -69,22 +65,6 @@ const DataQualityRoutes = () => (
);
-const CoverageOverviewRoutes = () => {
- const isDetectionsCoverageOverviewEnabled = useIsExperimentalFeatureEnabled(
- 'detectionsCoverageOverview'
- );
-
- return isDetectionsCoverageOverviewEnabled ? (
-
-
-
-
-
- ) : (
-
- );
-};
-
export const routes: SecuritySubPluginRoutes = [
{
path: OVERVIEW_PATH,
@@ -106,8 +86,4 @@ export const routes: SecuritySubPluginRoutes = [
path: DATA_QUALITY_PATH,
component: DataQualityRoutes,
},
- {
- path: COVERAGE_OVERVIEW_PATH,
- component: CoverageOverviewRoutes,
- },
];
diff --git a/x-pack/plugins/security_solution/public/rules/links.ts b/x-pack/plugins/security_solution/public/rules/links.ts
index 4e6482ab67bbd..276a4f1b2f7af 100644
--- a/x-pack/plugins/security_solution/public/rules/links.ts
+++ b/x-pack/plugins/security_solution/public/rules/links.ts
@@ -13,13 +13,22 @@ import {
RULES_LANDING_PATH,
RULES_ADD_PATH,
SERVER_APP_ID,
+ COVERAGE_OVERVIEW_PATH,
} from '../../common/constants';
-import { ADD_RULES, CREATE_NEW_RULE, EXCEPTIONS, RULES, SIEM_RULES } from '../app/translations';
+import {
+ ADD_RULES,
+ COVERAGE_OVERVIEW,
+ CREATE_NEW_RULE,
+ EXCEPTIONS,
+ RULES,
+ SIEM_RULES,
+} from '../app/translations';
import { SecurityPageName } from '../app/types';
import { benchmarksLink } from '../cloud_security_posture/links';
import type { LinkItem } from '../common/links';
import { IconConsoleCloud } from '../common/icons/console_cloud';
import { IconRollup } from '../common/icons/rollup';
+import { IconDashboards } from '../common/icons/dashboards';
export const links: LinkItem = {
id: SecurityPageName.rulesLanding,
@@ -78,6 +87,25 @@ export const links: LinkItem = {
],
},
benchmarksLink,
+ {
+ id: SecurityPageName.coverageOverview,
+ title: COVERAGE_OVERVIEW,
+ landingIcon: IconDashboards,
+ description: i18n.translate(
+ 'xpack.securitySolution.appLinks.coverageOverviewDashboardDescription',
+ {
+ defaultMessage: 'Review and maintain your protections MITRE ATT&CKĀ® coverage',
+ }
+ ),
+ path: COVERAGE_OVERVIEW_PATH,
+ capabilities: [`${SERVER_APP_ID}.show`],
+ globalSearchKeywords: [
+ i18n.translate('xpack.securitySolution.appLinks.coverageOverviewDashboard', {
+ defaultMessage: 'MITRE ATT&CK Coverage',
+ }),
+ ],
+ experimentalKey: 'detectionsCoverageOverview',
+ },
],
categories: [
{
@@ -90,5 +118,11 @@ export const links: LinkItem = {
SecurityPageName.exceptions,
],
},
+ {
+ label: i18n.translate('xpack.securitySolution.appLinks.category.discover', {
+ defaultMessage: 'Discover',
+ }),
+ linkIds: [SecurityPageName.coverageOverview],
+ },
],
};
diff --git a/x-pack/plugins/security_solution/public/rules/routes.tsx b/x-pack/plugins/security_solution/public/rules/routes.tsx
index e5f3360f189d5..aefdd505c95e0 100644
--- a/x-pack/plugins/security_solution/public/rules/routes.tsx
+++ b/x-pack/plugins/security_solution/public/rules/routes.tsx
@@ -10,7 +10,12 @@ import { Routes, Route } from '@kbn/shared-ux-router';
import { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
import * as i18n from './translations';
-import { RULES_LANDING_PATH, RULES_PATH, SecurityPageName } from '../../common/constants';
+import {
+ COVERAGE_OVERVIEW_PATH,
+ RULES_LANDING_PATH,
+ RULES_PATH,
+ SecurityPageName,
+} from '../../common/constants';
import { NotFoundPage } from '../app/404';
import { RulesPage } from '../detection_engine/rule_management_ui/pages/rule_management';
import { CreateRulePage } from '../detection_engine/rule_creation_ui/pages/rule_creation';
@@ -26,6 +31,8 @@ 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 = [
{
@@ -102,6 +109,22 @@ const RulesContainerComponent: React.FC = () => {
const Rules = React.memo(RulesContainerComponent);
+const CoverageOverviewRoutes = () => {
+ const isDetectionsCoverageOverviewEnabled = useIsExperimentalFeatureEnabled(
+ 'detectionsCoverageOverview'
+ );
+
+ return isDetectionsCoverageOverviewEnabled ? (
+
+
+
+
+
+ ) : (
+
+ );
+};
+
export const routes: SecuritySubPluginRoutes = [
{
path: RULES_LANDING_PATH,
@@ -111,4 +134,8 @@ export const routes: SecuritySubPluginRoutes = [
path: RULES_PATH,
component: Rules,
},
+ {
+ path: COVERAGE_OVERVIEW_PATH,
+ component: CoverageOverviewRoutes,
+ },
];