From ec849e5bd89b552b1c4cc8123f268bd422386d4b Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Mon, 14 Nov 2022 07:32:52 -0500 Subject: [PATCH] [Security Solution] [Exceptions] Adds a new react route for viewing details about an individual exception list (#144754) ## Summary Clicking on the link for an individual exception list will route users to the detail view. Currently only displays the list id on the detail view. Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/typescript_types/index.ts | 4 +-- .../src/get_general_filters/index.test.ts | 16 +++++++++++ .../src/get_general_filters/index.ts | 10 ++++--- .../security_solution/common/constants.ts | 1 + .../public/app/deep_links/index.ts | 9 +++++++ .../components/exceptions_list_card/index.tsx | 17 +++++++++--- .../hooks/use_exceptions_list.card/index.tsx | 1 + .../pages/shared_lists/detail_view.tsx | 27 +++++++++++++++++++ .../exceptions/pages/shared_lists/index.tsx | 6 +++-- .../public/exceptions/routes.tsx | 11 ++++++++ 10 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/detail_view.tsx diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts index 14118d9eb1d45..ad84d295cff84 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts @@ -8,7 +8,7 @@ import type { Filter } from '@kbn/es-query'; import { NamespaceType } from '../common/default_namespace'; -import { ExceptionListType } from '../common/exception_list'; +import { ExceptionListType, ExceptionListTypeEnum } from '../common/exception_list'; import { Page } from '../common/page'; import { PerPage } from '../common/per_page'; import { TotalOrUndefined } from '../common/total'; @@ -32,7 +32,7 @@ export interface ExceptionListFilter { name?: string | null; list_id?: string | null; created_by?: string | null; - type?: string | null; + types?: ExceptionListTypeEnum[] | null; tags?: string | null; } diff --git a/packages/kbn-securitysolution-list-utils/src/get_general_filters/index.test.ts b/packages/kbn-securitysolution-list-utils/src/get_general_filters/index.test.ts index 8786b48b73c82..fa5164540761b 100644 --- a/packages/kbn-securitysolution-list-utils/src/get_general_filters/index.test.ts +++ b/packages/kbn-securitysolution-list-utils/src/get_general_filters/index.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { getGeneralFilters } from '.'; describe('getGeneralFilters', () => { @@ -33,4 +34,19 @@ describe('getGeneralFilters', () => { '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample)' ); }); + + test('it properly formats filters when two types are passed in', () => { + const filters = getGeneralFilters( + { + created_by: 'moi', + name: 'Sample', + types: [ExceptionListTypeEnum.DETECTION, ExceptionListTypeEnum.RULE_DEFAULT], + }, + ['exception-list', 'exception-list-agnostic'] + ); + + expect(filters).toEqual( + '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (exception-list.attributes.type:detection OR exception-list.attributes.type:rule_default OR exception-list-agnostic.attributes.type:detection OR exception-list-agnostic.attributes.type:rule_default)' + ); + }); }); diff --git a/packages/kbn-securitysolution-list-utils/src/get_general_filters/index.ts b/packages/kbn-securitysolution-list-utils/src/get_general_filters/index.ts index f44e37e547fe9..0397310e838d3 100644 --- a/packages/kbn-securitysolution-list-utils/src/get_general_filters/index.ts +++ b/packages/kbn-securitysolution-list-utils/src/get_general_filters/index.ts @@ -7,6 +7,7 @@ */ import { ExceptionListFilter } from '@kbn/securitysolution-io-ts-list-types'; +import { isArray } from 'lodash'; import { get } from 'lodash/fp'; import { SavedObjectType } from '../types'; @@ -17,11 +18,14 @@ export const getGeneralFilters = ( return Object.keys(filters) .map((filterKey) => { const value = get(filterKey, filters); - if (value != null && value.trim() !== '') { + if (isArray(value) || (value != null && value.trim() !== '')) { const filtersByNamespace = namespaceTypes .map((namespace) => { - const fieldToSearch = filterKey === 'name' ? 'name.text' : filterKey; - return `${namespace}.attributes.${fieldToSearch}:${value}`; + const fieldToSearch = + filterKey === 'name' ? 'name.text' : filterKey === 'types' ? 'type' : filterKey; + return isArray(value) + ? value.map((val) => `${namespace}.attributes.${fieldToSearch}:${val}`).join(' OR ') + : `${namespace}.attributes.${fieldToSearch}:${value}`; }) .join(' OR '); return `(${filtersByNamespace})`; diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index d3893491533d2..e88580e1a338f 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -96,6 +96,7 @@ export enum SecurityPageName { endpoints = 'endpoints', eventFilters = 'event_filters', exceptions = 'exceptions', + sharedExceptionListDetails = 'shared-exception-list-details', exploreLanding = 'explore', hostIsolationExceptions = 'host_isolation_exceptions', hosts = 'hosts', 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 170e06742dcb0..8848f7bddc3d9 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,6 +234,15 @@ 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/exceptions/components/exceptions_list_card/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx index 7ade4b41cb0eb..5ff6b38484cf4 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx @@ -18,7 +18,6 @@ import { EuiButtonIcon, } from '@elastic/eui'; import { css } from '@emotion/react'; -import type { HttpSetup } from '@kbn/core-http-browser'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; import { HeaderMenu } from '@kbn/securitysolution-exception-list-components'; @@ -31,10 +30,11 @@ import { TitleBadge } from '../title_badge'; import * as i18n from '../../translations'; import { ListExceptionItems } from '../list_exception_items'; import { useExceptionsListCard } from '../../hooks/use_exceptions_list.card'; +import { useGetSecuritySolutionLinkProps } from '../../../common/components/links'; +import { SecurityPageName } from '../../../../common/constants'; interface ExceptionsListCardProps { exceptionsList: ExceptionListInfo; - http: HttpSetup; handleDelete: ({ id, listId, @@ -104,6 +104,12 @@ export const ExceptionsListCard = memo( handleDelete, }); + // routes to x-pack/plugins/security_solution/public/exceptions/routes.tsx + const { onClick: goToExceptionDetail } = useGetSecuritySolutionLinkProps()({ + deepLinkId: SecurityPageName.sharedExceptionListDetails, + path: `/exceptions/shared/${exceptionsList.list_id}`, + }); + return ( @@ -131,7 +137,12 @@ export const ExceptionsListCard = memo( > - {listName} + + {listName} + 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 0602431f46901..882b94772ab71 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 @@ -54,6 +54,7 @@ export const useExceptionsListCard = ({ setExceptionToEdit(exception); setShowEditExceptionFlyout(true); }; + const { lastUpdated, exceptionViewerStatus, diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/detail_view.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/detail_view.tsx new file mode 100644 index 0000000000000..3a438dcecd048 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/detail_view.tsx @@ -0,0 +1,27 @@ +/* + * 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 React, { memo } from 'react'; +import { useParams } from 'react-router-dom'; +import { EuiTitle } from '@elastic/eui'; +import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../common/endpoint/service/artifacts/constants'; +import { NotFoundPage } from '../../../app/404'; + +export const ExceptionListsDetailView = memo(() => { + const { exceptionListId: listId } = useParams<{ + exceptionListId: string; + }>(); + return ALL_ENDPOINT_ARTIFACT_LIST_IDS.includes(listId) ? ( + + ) : ( + +

{listId}

+
+ ); +}); + +ExceptionListsDetailView.displayName = 'ExceptionListsDetailView'; diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx index 584cc5d032a44..0218dda4dbf5f 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx @@ -25,6 +25,7 @@ import { } from '@elastic/eui'; import type { NamespaceType, ExceptionListFilter } from '@kbn/securitysolution-io-ts-list-types'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { useApi, useExceptionLists } from '@kbn/securitysolution-list-hooks'; import { AutoDownload } from '../../../common/components/auto_download/auto_download'; @@ -83,7 +84,9 @@ export const SharedLists = React.memo(() => { const [referenceModalState, setReferenceModalState] = useState( exceptionReferenceModalInitialState ); - const [filters, setFilters] = useState(undefined); + const [filters, setFilters] = useState({ + types: [ExceptionListTypeEnum.DETECTION, ExceptionListTypeEnum.ENDPOINT], + }); const [loadingExceptions, exceptions, pagination, setPagination, refreshExceptions] = useExceptionLists({ errorMessage: i18n.ERROR_EXCEPTION_LISTS, @@ -487,7 +490,6 @@ export const SharedLists = React.memo(() => { key={excList.list_id} data-test-subj="exceptionsListCard" readOnly={canUserREAD && !canUserCRUD} - http={http} exceptionsList={excList} handleDelete={handleDelete} handleExport={handleExport} diff --git a/x-pack/plugins/security_solution/public/exceptions/routes.tsx b/x-pack/plugins/security_solution/public/exceptions/routes.tsx index d91669f4f6c89..1516e48703dba 100644 --- a/x-pack/plugins/security_solution/public/exceptions/routes.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/routes.tsx @@ -11,6 +11,7 @@ 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 { ExceptionListsDetailView } from './pages/shared_lists/detail_view'; import { SharedLists } from './pages/shared_lists'; import { SpyRoute } from '../common/utils/route/spy_routes'; import { NotFoundPage } from '../app/404'; @@ -26,12 +27,22 @@ const ExceptionsRoutes = () => ( ); +const ExceptionsListDetailRoute = () => ( + + + + + + +); + const ExceptionsContainerComponent: React.FC = () => { useReadonlyHeader(i18n.READ_ONLY_BADGE_TOOLTIP); return ( + );