From 2bda8aadcf1ab68002956fc73f836bf5e195a182 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Thu, 21 Jul 2022 16:43:01 +0100 Subject: [PATCH] Refactoring network details to use useSearchStrategy (#135995) * useSearchStrategy * fix unit tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../network/containers/details/index.test.tsx | 70 +++++++++ .../network/containers/details/index.tsx | 142 ++++++------------ .../components/side_panel/index.test.tsx | 13 ++ 3 files changed, 125 insertions(+), 100 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/network/containers/details/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/network/containers/details/index.test.tsx b/x-pack/plugins/security_solution/public/network/containers/details/index.test.tsx new file mode 100644 index 0000000000000..266fb15f4b40d --- /dev/null +++ b/x-pack/plugins/security_solution/public/network/containers/details/index.test.tsx @@ -0,0 +1,70 @@ +/* + * 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 { act, renderHook } from '@testing-library/react-hooks'; +import { TestProviders } from '../../../common/mock'; +import { ID, useNetworkDetails } from '.'; +import { useSearchStrategy } from '../../../common/containers/use_search_strategy'; + +jest.mock('../../../common/containers/use_search_strategy', () => ({ + useSearchStrategy: jest.fn(), +})); +const mockUseSearchStrategy = useSearchStrategy as jest.Mock; +const mockSearch = jest.fn(); + +const defaultProps = { + id: ID, + indexNames: ['fakebeat-*'], + ip: '192.168.1.1', + skip: false, +}; + +describe('useNetworkDetails', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockUseSearchStrategy.mockReturnValue({ + loading: false, + result: { + networkDetails: {}, + }, + search: mockSearch, + refetch: jest.fn(), + inspect: {}, + }); + }); + + it('runs search', () => { + renderHook(() => useNetworkDetails(defaultProps), { + wrapper: TestProviders, + }); + + expect(mockSearch).toHaveBeenCalled(); + }); + + it('does not run search when skip = true', () => { + const props = { + ...defaultProps, + skip: true, + }; + renderHook(() => useNetworkDetails(props), { + wrapper: TestProviders, + }); + + expect(mockSearch).not.toHaveBeenCalled(); + }); + it('skip = true will cancel any running request', () => { + const props = { + ...defaultProps, + }; + const { rerender } = renderHook(() => useNetworkDetails(props), { + wrapper: TestProviders, + }); + props.skip = true; + act(() => rerender()); + expect(mockUseSearchStrategy).toHaveBeenCalledTimes(2); + expect(mockUseSearchStrategy.mock.calls[1][0].abort).toEqual(true); + }); +}); diff --git a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx index 65d5cd3978991..1ab84d9f0fe37 100644 --- a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx @@ -5,25 +5,17 @@ * 2.0. */ -import { noop } from 'lodash/fp'; -import { useState, useEffect, useCallback, useRef } from 'react'; -import deepEqual from 'fast-deep-equal'; -import { Subscription } from 'rxjs'; +import { useEffect, useMemo } from 'react'; -import { isCompleteResponse, isErrorResponse } from '@kbn/data-plugin/common'; import type { ESTermQuery } from '../../../../common/typed_json'; import type { inputsModel } from '../../../common/store'; -import { useKibana } from '../../../common/lib/kibana'; import { createFilter } from '../../../common/containers/helpers'; -import type { - NetworkDetailsRequestOptions, - NetworkDetailsStrategyResponse, -} from '../../../../common/search_strategy'; +import type { NetworkDetailsStrategyResponse } from '../../../../common/search_strategy'; import { NetworkQueries } from '../../../../common/search_strategy'; import * as i18n from './translations'; -import { getInspectResponse } from '../../../helpers'; import type { InspectResponse } from '../../../types'; -import { useAppToasts } from '../../../common/hooks/use_app_toasts'; + +import { useSearchStrategy } from '../../../common/containers/use_search_strategy'; export const ID = 'networkDetailsQuery'; @@ -36,111 +28,61 @@ export interface NetworkDetailsArgs { } interface UseNetworkDetails { + filterQuery?: ESTermQuery | string; id?: string; - ip: string; indexNames: string[]; - filterQuery?: ESTermQuery | string; + ip: string; skip: boolean; } export const useNetworkDetails = ({ filterQuery, - indexNames, id = ID, - skip, + indexNames, ip, + skip, }: UseNetworkDetails): [boolean, NetworkDetailsArgs] => { - const { data } = useKibana().services; - const refetch = useRef(noop); - const abortCtrl = useRef(new AbortController()); - const searchSubscription$ = useRef(new Subscription()); - const [loading, setLoading] = useState(false); - - const [networkDetailsRequest, setNetworkDetailsRequest] = - useState(null); - - const [networkDetailsResponse, setNetworkDetailsResponse] = useState({ - networkDetails: {}, - id, - inspect: { - dsl: [], - response: [], + const { + loading, + result: response, + search, + refetch, + inspect, + } = useSearchStrategy({ + factoryQueryType: NetworkQueries.details, + initialResult: { + networkDetails: {}, }, - isInspected: false, - refetch: refetch.current, + errorMessage: i18n.ERROR_NETWORK_DETAILS, + abort: skip, }); - const { addError, addWarning } = useAppToasts(); - const networkDetailsSearch = useCallback( - (request: NetworkDetailsRequestOptions | null) => { - if (request == null || skip) { - return; - } - const asyncSearch = async () => { - abortCtrl.current = new AbortController(); - setLoading(true); - searchSubscription$.current = data.search - .search(request, { - strategy: 'securitySolutionSearchStrategy', - abortSignal: abortCtrl.current.signal, - }) - .subscribe({ - next: (response) => { - if (isCompleteResponse(response)) { - setLoading(false); - setNetworkDetailsResponse((prevResponse) => ({ - ...prevResponse, - networkDetails: response.networkDetails, - inspect: getInspectResponse(response, prevResponse.inspect), - refetch: refetch.current, - })); - searchSubscription$.current.unsubscribe(); - } else if (isErrorResponse(response)) { - setLoading(false); - addWarning(i18n.ERROR_NETWORK_DETAILS); - searchSubscription$.current.unsubscribe(); - } - }, - error: (msg) => { - setLoading(false); - addError(msg, { - title: i18n.FAIL_NETWORK_DETAILS, - }); - searchSubscription$.current.unsubscribe(); - }, - }); - }; - searchSubscription$.current.unsubscribe(); - abortCtrl.current.abort(); - asyncSearch(); - refetch.current = asyncSearch; - }, - [data.search, addError, addWarning, skip] + const networkDetailsResponse = useMemo( + () => ({ + networkDetails: response.networkDetails, + id, + inspect, + isInspected: false, + refetch, + }), + [id, inspect, refetch, response.networkDetails] ); - useEffect(() => { - setNetworkDetailsRequest((prevRequest) => { - const myRequest = { - ...(prevRequest ?? {}), - defaultIndex: indexNames, - factoryQueryType: NetworkQueries.details, - filterQuery: createFilter(filterQuery), - ip, - }; - if (!deepEqual(prevRequest, myRequest)) { - return myRequest; - } - return prevRequest; - }); - }, [indexNames, filterQuery, ip, id]); + const networkDetailsRequest = useMemo( + () => ({ + defaultIndex: indexNames, + factoryQueryType: NetworkQueries.details, + filterQuery: createFilter(filterQuery), + ip, + }), + [filterQuery, indexNames, ip] + ); useEffect(() => { - networkDetailsSearch(networkDetailsRequest); - return () => { - searchSubscription$.current.unsubscribe(); - abortCtrl.current.abort(); - }; - }, [networkDetailsRequest, networkDetailsSearch]); + if (!skip && networkDetailsRequest) { + search(networkDetailsRequest); + } + }, [networkDetailsRequest, search, skip]); return [loading, networkDetailsResponse]; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx index ce6c27bed24b7..4b32c1b6af078 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx @@ -260,12 +260,25 @@ describe('Details Panel Component', () => { describe('DetailsPanel:NetworkDetails: rendering', () => { beforeEach(() => { + mockUseSearchStrategy.mockReturnValue({ + loading: true, + result: { + networkDetails: {}, + }, + search: jest.fn(), + refetch: jest.fn(), + inspect: {}, + }); const mockState = { ...state }; mockState.timeline.timelineById[TimelineId.active].expandedDetail = networkExpandedDetail; mockState.timeline.timelineById.test.expandedDetail = networkExpandedDetail; store = createStore(mockState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); }); + afterEach(() => { + mockUseSearchStrategy.mockReset(); + }); + test('it should render the Network Details view in the Details Panel when the panelView is networkDetail and the ip is set', () => { const wrapper = mount(