Skip to content

Commit

Permalink
[Security Solution] [Exceptions] Adds a new react route for viewing d…
Browse files Browse the repository at this point in the history
…etails 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 <[email protected]>
  • Loading branch information
dhurley14 and kibanamachine authored Nov 14, 2022
1 parent 2d5709f commit ec849e5
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import { getGeneralFilters } from '.';

describe('getGeneralFilters', () => {
Expand Down Expand Up @@ -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)'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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})`;
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
],
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -104,6 +104,12 @@ export const ExceptionsListCard = memo<ExceptionsListCardProps>(
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 (
<EuiFlexGroup gutterSize="none">
<EuiFlexItem>
Expand Down Expand Up @@ -131,7 +137,12 @@ export const ExceptionsListCard = memo<ExceptionsListCardProps>(
>
<EuiFlexItem grow>
<EuiText size="m">
<EuiLink data-test-subj="exception-list-name">{listName}</EuiLink>
<EuiLink
data-test-subj="exception-list-name"
onClick={goToExceptionDetail}
>
{listName}
</EuiLink>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const useExceptionsListCard = ({
setExceptionToEdit(exception);
setShowEditExceptionFlyout(true);
};

const {
lastUpdated,
exceptionViewerStatus,
Expand Down
Original file line number Diff line number Diff line change
@@ -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) ? (
<NotFoundPage />
) : (
<EuiTitle>
<h2>{listId}</h2>
</EuiTitle>
);
});

ExceptionListsDetailView.displayName = 'ExceptionListsDetailView';
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -83,7 +84,9 @@ export const SharedLists = React.memo(() => {
const [referenceModalState, setReferenceModalState] = useState<ReferenceModalState>(
exceptionReferenceModalInitialState
);
const [filters, setFilters] = useState<ExceptionListFilter | undefined>(undefined);
const [filters, setFilters] = useState<ExceptionListFilter | undefined>({
types: [ExceptionListTypeEnum.DETECTION, ExceptionListTypeEnum.ENDPOINT],
});
const [loadingExceptions, exceptions, pagination, setPagination, refreshExceptions] =
useExceptionLists({
errorMessage: i18n.ERROR_EXCEPTION_LISTS,
Expand Down Expand Up @@ -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}
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/security_solution/public/exceptions/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -26,12 +27,22 @@ const ExceptionsRoutes = () => (
</PluginTemplateWrapper>
);

const ExceptionsListDetailRoute = () => (
<PluginTemplateWrapper>
<TrackApplicationView viewId={SecurityPageName.sharedExceptionListDetails}>
<ExceptionListsDetailView />
<SpyRoute pageName={SecurityPageName.sharedExceptionListDetails} />
</TrackApplicationView>
</PluginTemplateWrapper>
);

const ExceptionsContainerComponent: React.FC = () => {
useReadonlyHeader(i18n.READ_ONLY_BADGE_TOOLTIP);

return (
<Switch>
<Route path={EXCEPTIONS_PATH} exact component={ExceptionsRoutes} />
<Route path={'/exceptions/shared/:exceptionListId'} component={ExceptionsListDetailRoute} />
<Route component={NotFoundPage} />
</Switch>
);
Expand Down

0 comments on commit ec849e5

Please sign in to comment.