diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index aec2bc40a4824e..0981293a1607ab 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -97,7 +97,6 @@ export enum SecurityPageName { endpoints = 'endpoints', eventFilters = 'event_filters', exceptions = 'exceptions', - sharedExceptionListDetails = 'shared-exception-list-details', exploreLanding = 'explore', hostIsolationExceptions = 'host_isolation_exceptions', hosts = 'hosts', @@ -150,6 +149,7 @@ export const ALERTS_PATH = '/alerts' as const; export const RULES_PATH = '/rules' as const; export const RULES_CREATE_PATH = `${RULES_PATH}/create` as const; export const EXCEPTIONS_PATH = '/exceptions' as const; +export const EXCEPTION_LIST_DETAIL_PATH = `${EXCEPTIONS_PATH}/details/:detailName` as const; export const HOSTS_PATH = '/hosts' as const; export const USERS_PATH = '/users' as const; export const KUBERNETES_PATH = '/kubernetes' as const; diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 8848f7bddc3d95..170e06742dcb0c 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -234,15 +234,6 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ defaultMessage: 'Exception lists', }), ], - deepLinks: [ - { - id: SecurityPageName.sharedExceptionListDetails, - title: 'List Details', - path: '/exceptions/shared/:exceptionListId', - navLinkStatus: AppNavLinkStatus.hidden, - searchable: false, - }, - ], }, ], }, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 84aec148913280..0cb13b5bcc4a8b 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -133,6 +133,11 @@ const rulesBReadcrumb = { href: 'securitySolutionUI/rules', }; +const exceptionsBReadcrumb = { + text: 'Rule Exceptions', + href: 'securitySolutionUI/exceptions', +}; + const manageBreadcrumbs = { text: 'Manage', href: 'securitySolutionUI/administration', @@ -433,6 +438,32 @@ describe('Navigation Breadcrumbs', () => { }, ]); }); + + test('should return Exceptions breadcrumbs when supplied exception Details pageName', () => { + const mockListName = 'new shared list'; + const breadcrumbs = getBreadcrumbsForRoute( + { + ...getMockObject( + SecurityPageName.exceptions, + `/exceptions/details/${mockListName}`, + undefined + ), + state: { + listName: mockListName, + }, + }, + getSecuritySolutionUrl, + false + ); + expect(breadcrumbs).toEqual([ + securityBreadCrumb, + exceptionsBReadcrumb, + { + text: mockListName, + href: ``, + }, + ]); + }); }); describe('setBreadcrumbs()', () => { @@ -773,6 +804,31 @@ describe('Navigation Breadcrumbs', () => { }, ]); }); + test('should return Exceptions breadcrumbs when supplied exception Details pageName', () => { + const mockListName = 'new shared list'; + const breadcrumbs = getBreadcrumbsForRoute( + { + ...getMockObject( + SecurityPageName.exceptions, + `/exceptions/details/${mockListName}`, + undefined + ), + state: { + listName: mockListName, + }, + }, + getSecuritySolutionUrl, + false + ); + expect(breadcrumbs).toEqual([ + securityBreadCrumb, + exceptionsBReadcrumb, + { + text: mockListName, + href: ``, + }, + ]); + }); }); describe('setBreadcrumbs()', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts index afcaff3f3d0658..290f991b76ed83 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts @@ -13,6 +13,7 @@ import type { StartServices } from '../../../../types'; import { getTrailingBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../../hosts/pages/details/utils'; import { getTrailingBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../network/pages/details'; import { getTrailingBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../../detections/pages/detection_engine/rules/utils'; +import { getTrailingBreadcrumbs as geExceptionsBreadcrumbs } from '../../../../exceptions/utils/pages.utils'; import { getTrailingBreadcrumbs as getUsersBreadcrumbs } from '../../../../users/pages/details/utils'; import { getTrailingBreadcrumbs as getKubernetesBreadcrumbs } from '../../../../kubernetes/pages/utils/breadcrumbs'; import { getTrailingBreadcrumbs as getAlertDetailBreadcrumbs } from '../../../../detections/pages/alert_details/utils/breadcrumbs'; @@ -131,6 +132,8 @@ const getTrailingBreadcrumbsForRoutes = ( return getDetectionRulesBreadcrumbs(spyState, getSecuritySolutionUrl); } + if (isExceptionRoutes(spyState)) return geExceptionsBreadcrumbs(spyState, getSecuritySolutionUrl); + if (isKubernetesRoutes(spyState)) { return getKubernetesBreadcrumbs(spyState, getSecuritySolutionUrl); } @@ -162,6 +165,9 @@ const isRulesRoutes = (spyState: RouteSpyState): spyState is AdministrationRoute spyState.pageName === SecurityPageName.rules || spyState.pageName === SecurityPageName.rulesCreate; +const isExceptionRoutes = (spyState: RouteSpyState) => + spyState.pageName === SecurityPageName.exceptions; + const isCloudSecurityPostureManagedRoutes = (spyState: RouteSpyState) => spyState.pageName === SecurityPageName.cloudSecurityPostureRules; diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx index 746f49b3732840..3d7a0c500edace 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx @@ -163,8 +163,8 @@ export const useExceptionsListCard = ({ // routes to x-pack/plugins/security_solution/public/exceptions/routes.tsx const { onClick: goToExceptionDetail } = useGetSecuritySolutionLinkProps()({ - deepLinkId: SecurityPageName.sharedExceptionListDetails, - path: `/exceptions/shared/${exceptionsList.list_id}`, + deepLinkId: SecurityPageName.exceptions, + path: `/details/${exceptionsList.list_id}`, }); return { listId, diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts index 9342ec06df291d..0e574c8b19039f 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts @@ -53,8 +53,8 @@ export const useListDetailsView = () => { const { exportExceptionList, deleteExceptionList } = useApi(http); - const { exceptionListId } = useParams<{ - exceptionListId: string; + const { detailName: exceptionListId } = useParams<{ + detailName: string; }>(); const [{ loading: userInfoLoading, canUserCRUD, canUserREAD }] = useUserData(); diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx index 7697ba01427d36..cd140a1160a440 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import type { FC } from 'react'; import { @@ -13,6 +13,8 @@ import { ViewerStatus, } from '@kbn/securitysolution-exception-list-components'; import { EuiLoadingContent } from '@elastic/eui'; +import { SecurityPageName } from '../../../../common/constants'; +import { SpyRoute } from '../../../common/utils/route/spy_routes'; import { ReferenceErrorModal } from '../../../detections/components/value_lists_management_flyout/reference_error_modal'; import type { Rule } from '../../../detection_engine/rule_management/logic/types'; import { MissingPrivilegesCallOut } from '../../../detections/components/callouts/missing_privileges_callout'; @@ -53,53 +55,90 @@ export const ListsDetailViewComponent: FC = () => { handleReferenceDelete, } = useListDetailsView(); - if (viewerStatus === ViewerStatus.ERROR) - return ; + const detailsViewContent = useMemo(() => { + if (viewerStatus === ViewerStatus.ERROR) + return ; - if (isLoading) return ; + if (isLoading) return ; - if (invalidListId || !listName || !list) return ; - return ( - <> - - + if (invalidListId || !listName || !list) return ; + return ( + <> + + - - - - {showManageRulesFlyout ? ( - + + - ) : null} + {showManageRulesFlyout ? ( + + ) : null} + + ); + }, [ + canUserEditList, + disableManageButton, + exportedList, + headerBackOptions, + invalidListId, + isLoading, + isReadOnly, + linkedRules, + list, + listDescription, + listId, + listName, + referenceModalState.contentText, + referenceModalState.rulesReferences, + refreshExceptions, + showManageButtonLoader, + showManageRulesFlyout, + showReferenceErrorModal, + viewerStatus, + onCancelManageRules, + onEditListDetails, + onExportList, + onManageRules, + onRuleSelectionChange, + onSaveManageRules, + handleCloseReferenceErrorModal, + handleDelete, + handleReferenceDelete, + ]); + return ( + <> + + {detailsViewContent} ); }; diff --git a/x-pack/plugins/security_solution/public/exceptions/routes.tsx b/x-pack/plugins/security_solution/public/exceptions/routes.tsx index 13cc29411f97c0..dd94e51f6c33ba 100644 --- a/x-pack/plugins/security_solution/public/exceptions/routes.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/routes.tsx @@ -10,7 +10,11 @@ import { Route } from '@kbn/kibana-react-plugin/public'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import * as i18n from './translations'; -import { EXCEPTIONS_PATH, SecurityPageName } from '../../common/constants'; +import { + EXCEPTIONS_PATH, + SecurityPageName, + EXCEPTION_LIST_DETAIL_PATH, +} from '../../common/constants'; import { SharedLists, ListsDetailView } from './pages'; import { SpyRoute } from '../common/utils/route/spy_routes'; @@ -29,9 +33,8 @@ const ExceptionsRoutes = () => ( const ExceptionsListDetailRoute = () => ( - + - ); @@ -42,7 +45,7 @@ const ExceptionsContainerComponent: React.FC = () => { return ( - + ); diff --git a/x-pack/plugins/security_solution/public/exceptions/utils/pages.utils.ts b/x-pack/plugins/security_solution/public/exceptions/utils/pages.utils.ts new file mode 100644 index 00000000000000..9c1a3289aca6fe --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/utils/pages.utils.ts @@ -0,0 +1,30 @@ +/* + * 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 type { ChromeBreadcrumb } from '@kbn/core/public'; +import { EXCEPTIONS_PATH } from '../../../common/constants'; +import type { GetSecuritySolutionUrl } from '../../common/components/link_to'; +import type { RouteSpyState } from '../../common/utils/route/types'; + +const isListDetailPage = (pathname: string) => + pathname.includes(EXCEPTIONS_PATH) && pathname.includes('/details'); + +export const getTrailingBreadcrumbs = ( + params: RouteSpyState, + getSecuritySolutionUrl: GetSecuritySolutionUrl +): ChromeBreadcrumb[] => { + let breadcrumb: ChromeBreadcrumb[] = []; + + if (isListDetailPage(params.pathName) && params.state?.listName) { + breadcrumb = [ + ...breadcrumb, + { + text: params.state.listName, + }, + ]; + } + return breadcrumb; +};