From 168e3dc5e979a677186a99d624b3bff0b6cd0545 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:56:55 -0400 Subject: [PATCH] [Security Solution] Coverage Overview follow-up (#164613) --- .../public/dashboards/links.ts | 2 -- .../pages/coverage_overview/constants.ts | 3 +- .../coverage_overview_dashboard.tsx | 17 +++++++-- .../coverage_overview_dashboard_context.tsx | 9 +++-- .../pages/coverage_overview/helpers.test.ts | 25 ++++++++++++- .../pages/coverage_overview/helpers.ts | 23 +++++++++--- .../rule_activity_filter.tsx | 3 +- .../coverage_overview/rule_source_filter.tsx | 3 +- .../coverage_overview/technique_panel.tsx | 11 ++++-- .../technique_panel_popover.test.tsx | 17 +++++++-- .../technique_panel_popover.tsx | 17 ++++++--- .../pages/coverage_overview/translations.ts | 17 ++++++++- .../public/overview/links.ts | 23 ------------ .../public/overview/routes.tsx | 24 ------------- .../security_solution/public/rules/links.ts | 36 ++++++++++++++++++- .../security_solution/public/rules/routes.tsx | 29 ++++++++++++++- 16 files changed, 181 insertions(+), 78 deletions(-) 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, + }, ];