Co-authored-by: Dmitry Shevchenko <dmshevch@gmail.com>
This commit is contained in:
parent
5d599f1e6a
commit
3a53492f29
7 changed files with 156 additions and 115 deletions
|
@ -40,7 +40,7 @@ export interface UseExceptionListsProps {
|
|||
http: HttpStart;
|
||||
namespaceTypes: NamespaceType[];
|
||||
notifications: NotificationsStart;
|
||||
pagination?: Pagination;
|
||||
initialPagination?: Pagination;
|
||||
showTrustedApps: boolean;
|
||||
showEventFilters: boolean;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type {
|
||||
ExceptionListSchema,
|
||||
UseExceptionListsProps,
|
||||
|
@ -17,7 +17,19 @@ import { fetchExceptionLists } from '@kbn/securitysolution-list-api';
|
|||
import { getFilters } from '@kbn/securitysolution-list-utils';
|
||||
|
||||
export type Func = () => void;
|
||||
export type ReturnExceptionLists = [boolean, ExceptionListSchema[], Pagination, Func | null];
|
||||
export type ReturnExceptionLists = [
|
||||
loading: boolean,
|
||||
exceptionLists: ExceptionListSchema[],
|
||||
pagination: Pagination,
|
||||
setPagination: React.Dispatch<React.SetStateAction<Pagination>>,
|
||||
fetchLists: Func | null
|
||||
];
|
||||
|
||||
const DEFAULT_PAGINATION = {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for fetching ExceptionLists
|
||||
|
@ -29,17 +41,13 @@ export type ReturnExceptionLists = [boolean, ExceptionListSchema[], Pagination,
|
|||
* @param notifications kibana service for displaying toasters
|
||||
* @param showTrustedApps boolean - include/exclude trusted app lists
|
||||
* @param showEventFilters boolean - include/exclude event filters lists
|
||||
* @param pagination
|
||||
* @param initialPagination
|
||||
*
|
||||
*/
|
||||
export const useExceptionLists = ({
|
||||
errorMessage,
|
||||
http,
|
||||
pagination = {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
initialPagination = DEFAULT_PAGINATION,
|
||||
filterOptions = {},
|
||||
namespaceTypes,
|
||||
notifications,
|
||||
|
@ -47,9 +55,9 @@ export const useExceptionLists = ({
|
|||
showEventFilters = false,
|
||||
}: UseExceptionListsProps): ReturnExceptionLists => {
|
||||
const [exceptionLists, setExceptionLists] = useState<ExceptionListSchema[]>([]);
|
||||
const [paginationInfo, setPagination] = useState<Pagination>(pagination);
|
||||
const [pagination, setPagination] = useState<Pagination>(initialPagination);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const fetchExceptionListsRef = useRef<Func | null>(null);
|
||||
const abortCtrlRef = useRef<AbortController>();
|
||||
|
||||
const namespaceTypesAsString = useMemo(() => namespaceTypes.join(','), [namespaceTypes]);
|
||||
const filters = useMemo(
|
||||
|
@ -58,66 +66,57 @@ export const useExceptionLists = ({
|
|||
[namespaceTypes, filterOptions, showTrustedApps, showEventFilters]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let isSubscribed = true;
|
||||
const abortCtrl = new AbortController();
|
||||
const fetchData = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const fetchData = async (): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
abortCtrlRef.current = new AbortController();
|
||||
|
||||
const { page, per_page: perPage, total, data } = await fetchExceptionLists({
|
||||
filters,
|
||||
http,
|
||||
namespaceTypes: namespaceTypesAsString,
|
||||
pagination: {
|
||||
page: pagination.page,
|
||||
perPage: pagination.perPage,
|
||||
},
|
||||
signal: abortCtrl.signal,
|
||||
const { page, per_page: perPage, total, data } = await fetchExceptionLists({
|
||||
filters,
|
||||
http,
|
||||
namespaceTypes: namespaceTypesAsString,
|
||||
pagination: {
|
||||
page: pagination.page,
|
||||
perPage: pagination.perPage,
|
||||
},
|
||||
signal: abortCtrlRef.current.signal,
|
||||
});
|
||||
|
||||
setPagination({
|
||||
page,
|
||||
perPage,
|
||||
total,
|
||||
});
|
||||
setExceptionLists(data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
if (error.name !== 'AbortError') {
|
||||
notifications.toasts.addError(error, {
|
||||
title: errorMessage,
|
||||
});
|
||||
|
||||
if (isSubscribed) {
|
||||
setPagination({
|
||||
page,
|
||||
perPage,
|
||||
total,
|
||||
});
|
||||
setExceptionLists(data);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isSubscribed) {
|
||||
notifications.toasts.addError(error, {
|
||||
title: errorMessage,
|
||||
});
|
||||
setExceptionLists([]);
|
||||
setPagination({
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
setExceptionLists([]);
|
||||
setPagination(DEFAULT_PAGINATION);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
|
||||
fetchExceptionListsRef.current = fetchData;
|
||||
return (): void => {
|
||||
isSubscribed = false;
|
||||
abortCtrl.abort();
|
||||
};
|
||||
}
|
||||
}, [
|
||||
errorMessage,
|
||||
notifications,
|
||||
filters,
|
||||
http,
|
||||
namespaceTypesAsString,
|
||||
notifications.toasts,
|
||||
pagination.page,
|
||||
pagination.perPage,
|
||||
filters,
|
||||
namespaceTypesAsString,
|
||||
http,
|
||||
]);
|
||||
|
||||
return [loading, exceptionLists, paginationInfo, fetchExceptionListsRef.current];
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
|
||||
return (): void => {
|
||||
abortCtrlRef.current?.abort();
|
||||
};
|
||||
}, [fetchData]);
|
||||
|
||||
return [loading, exceptionLists, pagination, setPagination, fetchData];
|
||||
};
|
||||
|
|
|
@ -12,6 +12,6 @@ import { getExceptionListSchemaMock } from './exception_list_schema.mock';
|
|||
export const getFoundExceptionListSchemaMock = (): FoundExceptionListSchema => ({
|
||||
data: [getExceptionListSchemaMock()],
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
per_page: 20,
|
||||
total: 1,
|
||||
});
|
||||
|
|
|
@ -41,13 +41,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: false,
|
||||
})
|
||||
|
@ -62,7 +62,8 @@ describe('useExceptionLists', () => {
|
|||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
null,
|
||||
expect.any(Function),
|
||||
expect.any(Function),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -77,13 +78,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: false,
|
||||
})
|
||||
|
@ -100,10 +101,11 @@ describe('useExceptionLists', () => {
|
|||
expectedListItemsResult,
|
||||
{
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
perPage: 20,
|
||||
total: 1,
|
||||
},
|
||||
result.current[3],
|
||||
expect.any(Function),
|
||||
expect.any(Function),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -117,13 +119,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: true,
|
||||
})
|
||||
|
@ -153,13 +155,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: false,
|
||||
})
|
||||
|
@ -189,13 +191,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: true,
|
||||
showTrustedApps: false,
|
||||
})
|
||||
|
@ -225,13 +227,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: false,
|
||||
})
|
||||
|
@ -264,13 +266,13 @@ describe('useExceptionLists', () => {
|
|||
name: 'Sample Endpoint',
|
||||
},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: false,
|
||||
})
|
||||
|
@ -302,9 +304,9 @@ describe('useExceptionLists', () => {
|
|||
errorMessage,
|
||||
filterOptions,
|
||||
http,
|
||||
initialPagination,
|
||||
namespaceTypes,
|
||||
notifications,
|
||||
pagination,
|
||||
showEventFilters,
|
||||
showTrustedApps,
|
||||
}) =>
|
||||
|
@ -312,9 +314,9 @@ describe('useExceptionLists', () => {
|
|||
errorMessage,
|
||||
filterOptions,
|
||||
http,
|
||||
initialPagination,
|
||||
namespaceTypes,
|
||||
notifications,
|
||||
pagination,
|
||||
showEventFilters,
|
||||
showTrustedApps,
|
||||
}),
|
||||
|
@ -323,13 +325,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: false,
|
||||
},
|
||||
|
@ -344,13 +346,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: false,
|
||||
});
|
||||
|
@ -372,13 +374,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: false,
|
||||
})
|
||||
|
@ -390,8 +392,8 @@ describe('useExceptionLists', () => {
|
|||
|
||||
expect(typeof result.current[3]).toEqual('function');
|
||||
|
||||
if (result.current[3] != null) {
|
||||
result.current[3]();
|
||||
if (result.current[4] != null) {
|
||||
result.current[4]();
|
||||
}
|
||||
// NOTE: Only need one call here because hook already initilaized
|
||||
await waitForNextUpdate();
|
||||
|
@ -411,13 +413,13 @@ describe('useExceptionLists', () => {
|
|||
errorMessage: 'Uh oh',
|
||||
filterOptions: {},
|
||||
http: mockKibanaHttpService,
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
pagination: {
|
||||
initialPagination: {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
total: 0,
|
||||
},
|
||||
namespaceTypes: ['single', 'agnostic'],
|
||||
notifications: mockKibanaNotificationsService,
|
||||
showEventFilters: false,
|
||||
showTrustedApps: false,
|
||||
})
|
||||
|
|
|
@ -15,8 +15,9 @@ import { FormatUrl } from '../../../../../../common/components/link_to';
|
|||
import * as i18n from './translations';
|
||||
import { ExceptionListInfo } from './use_all_exception_lists';
|
||||
import { ExceptionOverflowDisplay } from './exceptions_overflow_display';
|
||||
import { ExceptionsTableItem } from './types';
|
||||
|
||||
export type AllExceptionListsColumns = EuiBasicTableColumn<ExceptionListInfo>;
|
||||
export type AllExceptionListsColumns = EuiBasicTableColumn<ExceptionsTableItem>;
|
||||
|
||||
export const getAllExceptionListsColumns = (
|
||||
onExport: (arg: { id: string; listId: string; namespaceType: NamespaceType }) => () => void,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React, { useMemo, useEffect, useCallback, useState } from 'react';
|
||||
import {
|
||||
CriteriaWithPagination,
|
||||
EuiBasicTable,
|
||||
EuiEmptyPrompt,
|
||||
EuiLoadingContent,
|
||||
|
@ -37,6 +38,7 @@ import { SecurityPageName } from '../../../../../../../common/constants';
|
|||
import { useUserData } from '../../../../../components/user_info';
|
||||
import { userHasPermissions } from '../../helpers';
|
||||
import { useListsConfig } from '../../../../../containers/detection_engine/lists/use_lists_config';
|
||||
import { ExceptionsTableItem } from './types';
|
||||
|
||||
export type Func = () => Promise<void>;
|
||||
|
||||
|
@ -74,7 +76,13 @@ export const ExceptionListsTable = React.memo(() => {
|
|||
exceptionReferenceModalInitialState
|
||||
);
|
||||
const [filters, setFilters] = useState<ExceptionListFilter | undefined>(undefined);
|
||||
const [loadingExceptions, exceptions, pagination, refreshExceptions] = useExceptionLists({
|
||||
const [
|
||||
loadingExceptions,
|
||||
exceptions,
|
||||
pagination,
|
||||
setPagination,
|
||||
refreshExceptions,
|
||||
] = useExceptionLists({
|
||||
errorMessage: i18n.ERROR_EXCEPTION_LISTS,
|
||||
filterOptions: filters,
|
||||
http,
|
||||
|
@ -125,7 +133,7 @@ export const ExceptionListsTable = React.memo(() => {
|
|||
try {
|
||||
setDeletingListIds((ids) => [...ids, id]);
|
||||
if (refreshExceptions != null) {
|
||||
await refreshExceptions();
|
||||
refreshExceptions();
|
||||
}
|
||||
|
||||
if (exceptionsListsRef[id] != null && exceptionsListsRef[id].rules.length === 0) {
|
||||
|
@ -153,7 +161,7 @@ export const ExceptionListsTable = React.memo(() => {
|
|||
} catch (error) {
|
||||
handleDeleteError(error);
|
||||
} finally {
|
||||
setDeletingListIds((ids) => [...ids.filter((_id) => _id !== id)]);
|
||||
setDeletingListIds((ids) => ids.filter((_id) => _id !== id));
|
||||
}
|
||||
},
|
||||
[
|
||||
|
@ -326,11 +334,27 @@ export const ExceptionListsTable = React.memo(() => {
|
|||
setExportDownload({});
|
||||
}, []);
|
||||
|
||||
const tableItems = (exceptionListsWithRuleRefs ?? []).map((item) => ({
|
||||
...item,
|
||||
isDeleting: deletingListIds.includes(item.id),
|
||||
isExporting: exportingListIds.includes(item.id),
|
||||
}));
|
||||
const tableItems = useMemo<ExceptionsTableItem[]>(
|
||||
() =>
|
||||
(exceptionListsWithRuleRefs ?? []).map((item) => ({
|
||||
...item,
|
||||
isDeleting: deletingListIds.includes(item.id),
|
||||
isExporting: exportingListIds.includes(item.id),
|
||||
})),
|
||||
[deletingListIds, exceptionListsWithRuleRefs, exportingListIds]
|
||||
);
|
||||
|
||||
const handlePaginationChange = useCallback(
|
||||
(criteria: CriteriaWithPagination<ExceptionsTableItem>) => {
|
||||
const { index, size } = criteria.page;
|
||||
setPagination((currentPagination) => ({
|
||||
...currentPagination,
|
||||
perPage: size,
|
||||
page: index + 1,
|
||||
}));
|
||||
},
|
||||
[setPagination]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -367,14 +391,14 @@ export const ExceptionListsTable = React.memo(() => {
|
|||
numberSelectedItems={0}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
<EuiBasicTable
|
||||
<EuiBasicTable<ExceptionsTableItem>
|
||||
data-test-subj="exceptions-table"
|
||||
columns={exceptionsColumns}
|
||||
isSelectable={hasPermissions}
|
||||
itemId="id"
|
||||
items={tableItems}
|
||||
noItemsMessage={emptyPrompt}
|
||||
onChange={() => {}}
|
||||
onChange={handlePaginationChange}
|
||||
pagination={paginationMemo}
|
||||
/>
|
||||
</>
|
||||
|
@ -400,3 +424,5 @@ export const ExceptionListsTable = React.memo(() => {
|
|||
</>
|
||||
);
|
||||
});
|
||||
|
||||
ExceptionListsTable.displayName = 'ExceptionListsTable';
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 { ExceptionListInfo } from './use_all_exception_lists';
|
||||
|
||||
export interface ExceptionsTableItem extends ExceptionListInfo {
|
||||
isDeleting: boolean;
|
||||
isExporting: boolean;
|
||||
}
|
Loading…
Reference in a new issue