mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[8.6] [Security Solution][List Details Page]: Fix exception list details
page route and adding breadcrumb (#145605) (#145960)
# Backport This will backport the following commits from `main` to `8.6`: - [[Security Solution][List Details Page]: Fix `exception list details` page route and adding breadcrumb (#145605)](https://github.com/elastic/kibana/pull/145605) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Wafaa Nasr","email":"wafaa.nasr@elastic.co"},"sourceCommit":{"committedDate":"2022-11-21T15:38:33Z","message":"[Security Solution][List Details Page]: Fix `exception list details` page route and adding breadcrumb (#145605)\n\n## Summary\r\n\r\nAs per this\r\n[discussion](https://github.com/elastic/kibana/pull/145605#pullrequestreview-1186305242)\r\n\r\n- Remove the `Exception-List-details` definition from the [management\r\nlinks](bb77588350/x-pack/plugins/security_solution/public/app/deep_links/index.ts
)\r\nbecause it is a dynamic route\r\n\r\n- Use the `Rule Exceptions` page title for list details\r\n- Add breadcrumb for the details list page\r\n\r\nCo-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>","sha":"62d9dffbb2d806f35e76dff361fcf9df0b9ce90b","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Security Solution Platform","backport:prev-minor","v8.6.0","v8.7.0"],"number":145605,"url":"https://github.com/elastic/kibana/pull/145605","mergeCommit":{"message":"[Security Solution][List Details Page]: Fix `exception list details` page route and adding breadcrumb (#145605)\n\n## Summary\r\n\r\nAs per this\r\n[discussion](https://github.com/elastic/kibana/pull/145605#pullrequestreview-1186305242)\r\n\r\n- Remove the `Exception-List-details` definition from the [management\r\nlinks](bb77588350/x-pack/plugins/security_solution/public/app/deep_links/index.ts
)\r\nbecause it is a dynamic route\r\n\r\n- Use the `Rule Exceptions` page title for list details\r\n- Add breadcrumb for the details list page\r\n\r\nCo-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>","sha":"62d9dffbb2d806f35e76dff361fcf9df0b9ce90b"}},"sourceBranch":"main","suggestedTargetBranches":["8.6"],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/145605","number":145605,"mergeCommit":{"message":"[Security Solution][List Details Page]: Fix `exception list details` page route and adding breadcrumb (#145605)\n\n## Summary\r\n\r\nAs per this\r\n[discussion](https://github.com/elastic/kibana/pull/145605#pullrequestreview-1186305242)\r\n\r\n- Remove the `Exception-List-details` definition from the [management\r\nlinks](bb77588350/x-pack/plugins/security_solution/public/app/deep_links/index.ts
)\r\nbecause it is a dynamic route\r\n\r\n- Use the `Rule Exceptions` page title for list details\r\n- Add breadcrumb for the details list page\r\n\r\nCo-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>","sha":"62d9dffbb2d806f35e76dff361fcf9df0b9ce90b"}}]}] BACKPORT-->
This commit is contained in:
parent
db96bd0122
commit
acd9091616
9 changed files with 187 additions and 62 deletions
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -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()', () => {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 <EmptyViewerState isReadOnly={isReadOnly} viewerStatus={viewerStatus} />;
|
||||
const detailsViewContent = useMemo(() => {
|
||||
if (viewerStatus === ViewerStatus.ERROR)
|
||||
return <EmptyViewerState isReadOnly={isReadOnly} viewerStatus={viewerStatus} />;
|
||||
|
||||
if (isLoading) return <EuiLoadingContent lines={4} data-test-subj="loading" />;
|
||||
if (isLoading) return <EuiLoadingContent lines={4} data-test-subj="loading" />;
|
||||
|
||||
if (invalidListId || !listName || !list) return <NotFoundPage />;
|
||||
if (invalidListId || !listName || !list) return <NotFoundPage />;
|
||||
return (
|
||||
<>
|
||||
<MissingPrivilegesCallOut />
|
||||
<ExceptionListHeader
|
||||
name={listName}
|
||||
description={listDescription}
|
||||
listId={listId}
|
||||
linkedRules={linkedRules}
|
||||
isReadonly={isReadOnly}
|
||||
canUserEditList={canUserEditList}
|
||||
backOptions={headerBackOptions}
|
||||
securityLinkAnchorComponent={ListDetailsLinkAnchor}
|
||||
onEditListDetails={onEditListDetails}
|
||||
onExportList={onExportList}
|
||||
onDeleteList={handleDelete}
|
||||
onManageRules={onManageRules}
|
||||
/>
|
||||
|
||||
<AutoDownload blob={exportedList} name={listId} />
|
||||
<ListWithSearch list={list} refreshExceptions={refreshExceptions} isReadOnly={isReadOnly} />
|
||||
<ReferenceErrorModal
|
||||
cancelText={i18n.REFERENCE_MODAL_CANCEL_BUTTON}
|
||||
confirmText={i18n.REFERENCE_MODAL_CONFIRM_BUTTON}
|
||||
contentText={referenceModalState.contentText}
|
||||
onCancel={handleCloseReferenceErrorModal}
|
||||
onClose={handleCloseReferenceErrorModal}
|
||||
onConfirm={handleReferenceDelete}
|
||||
references={referenceModalState.rulesReferences}
|
||||
showModal={showReferenceErrorModal}
|
||||
titleText={i18n.REFERENCE_MODAL_TITLE}
|
||||
/>
|
||||
{showManageRulesFlyout ? (
|
||||
<ManageRules
|
||||
linkedRules={linkedRules as Rule[]}
|
||||
showButtonLoader={showManageButtonLoader}
|
||||
saveIsDisabled={disableManageButton}
|
||||
onSave={onSaveManageRules}
|
||||
onCancel={onCancelManageRules}
|
||||
onRuleSelectionChange={onRuleSelectionChange}
|
||||
/>
|
||||
) : 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 (
|
||||
<>
|
||||
<MissingPrivilegesCallOut />
|
||||
<ExceptionListHeader
|
||||
name={listName}
|
||||
description={listDescription}
|
||||
listId={listId}
|
||||
linkedRules={linkedRules}
|
||||
isReadonly={isReadOnly}
|
||||
canUserEditList={canUserEditList}
|
||||
backOptions={headerBackOptions}
|
||||
securityLinkAnchorComponent={ListDetailsLinkAnchor}
|
||||
onEditListDetails={onEditListDetails}
|
||||
onExportList={onExportList}
|
||||
onDeleteList={handleDelete}
|
||||
onManageRules={onManageRules}
|
||||
/>
|
||||
|
||||
<AutoDownload blob={exportedList} name={listId} />
|
||||
<ListWithSearch list={list} refreshExceptions={refreshExceptions} isReadOnly={isReadOnly} />
|
||||
<ReferenceErrorModal
|
||||
cancelText={i18n.REFERENCE_MODAL_CANCEL_BUTTON}
|
||||
confirmText={i18n.REFERENCE_MODAL_CONFIRM_BUTTON}
|
||||
contentText={referenceModalState.contentText}
|
||||
onCancel={handleCloseReferenceErrorModal}
|
||||
onClose={handleCloseReferenceErrorModal}
|
||||
onConfirm={handleReferenceDelete}
|
||||
references={referenceModalState.rulesReferences}
|
||||
showModal={showReferenceErrorModal}
|
||||
titleText={i18n.REFERENCE_MODAL_TITLE}
|
||||
/>
|
||||
{showManageRulesFlyout ? (
|
||||
<ManageRules
|
||||
linkedRules={linkedRules as Rule[]}
|
||||
showButtonLoader={showManageButtonLoader}
|
||||
saveIsDisabled={disableManageButton}
|
||||
onSave={onSaveManageRules}
|
||||
onCancel={onCancelManageRules}
|
||||
onRuleSelectionChange={onRuleSelectionChange}
|
||||
/>
|
||||
) : null}
|
||||
<SpyRoute pageName={SecurityPageName.exceptions} state={{ listName }} />
|
||||
{detailsViewContent}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 = () => (
|
||||
<PluginTemplateWrapper>
|
||||
<TrackApplicationView viewId={SecurityPageName.sharedExceptionListDetails}>
|
||||
<TrackApplicationView viewId={SecurityPageName.exceptions}>
|
||||
<ListsDetailView />
|
||||
<SpyRoute pageName={SecurityPageName.sharedExceptionListDetails} />
|
||||
</TrackApplicationView>
|
||||
</PluginTemplateWrapper>
|
||||
);
|
||||
|
@ -42,7 +45,7 @@ const ExceptionsContainerComponent: React.FC = () => {
|
|||
return (
|
||||
<Switch>
|
||||
<Route path={EXCEPTIONS_PATH} exact component={ExceptionsRoutes} />
|
||||
<Route path={'/exceptions/shared/:exceptionListId'} component={ExceptionsListDetailRoute} />
|
||||
<Route path={EXCEPTION_LIST_DETAIL_PATH} component={ExceptionsListDetailRoute} />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue