From a220883bcd6823db7cc50d8a55fff84edf4030fc Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Thu, 25 Jun 2020 18:08:17 +0200 Subject: [PATCH] [SIEM] Replace WithSource with useWithSource hook (#68722) --- .../detection_engine.test.tsx | 10 +- .../detection_engine/detection_engine.tsx | 142 ++++---- .../rules/details/index.test.tsx | 8 +- .../detection_engine/rules/details/index.tsx | 334 +++++++++--------- .../public/app/home/index.tsx | 38 +- .../draggable_wrapper_hover_content.test.tsx | 161 ++++----- .../draggable_wrapper_hover_content.tsx | 60 ++-- .../common/components/header_global/index.tsx | 96 +++-- .../public/common/components/top_n/index.tsx | 90 +++-- .../common/containers/global_time/index.tsx | 2 + .../common/containers/source/index.test.tsx | 67 ++-- .../public/common/containers/source/index.tsx | 169 ++++----- .../hosts/pages/details/details_tabs.test.tsx | 8 +- .../public/hosts/pages/details/index.tsx | 219 ++++++------ .../public/hosts/pages/hosts.test.tsx | 87 ++--- .../public/hosts/pages/hosts.tsx | 154 ++++---- .../__snapshots__/index.test.tsx.snap | 19 +- .../network/pages/ip_details/index.test.tsx | 41 +-- .../public/network/pages/ip_details/index.tsx | 304 ++++++++-------- .../public/network/pages/network.test.tsx | 69 ++-- .../public/network/pages/network.tsx | 178 +++++----- .../public/overview/pages/overview.test.tsx | 55 +-- .../public/overview/pages/overview.tsx | 171 +++++---- .../components/flyout/button/index.tsx | 25 +- .../timelines/components/timeline/index.tsx | 66 ++-- 25 files changed, 1215 insertions(+), 1358 deletions(-) diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx index 62b942d03591c1..d033bc25e98013 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.test.tsx @@ -12,9 +12,10 @@ import '../../../common/mock/match_media'; import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { DetectionEnginePageComponent } from './detection_engine'; import { useUserInfo } from '../../components/user_info'; +import { useWithSource } from '../../../common/containers/source'; jest.mock('../../components/user_info'); -jest.mock('../../../common/lib/kibana'); +jest.mock('../../../common/containers/source'); jest.mock('../../../common/components/link_to'); jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); @@ -30,7 +31,12 @@ describe('DetectionEnginePageComponent', () => { beforeAll(() => { (useParams as jest.Mock).mockReturnValue({}); (useUserInfo as jest.Mock).mockReturnValue({}); + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: {}, + }); }); + it('renders correctly', () => { const wrapper = shallow( { /> ); - expect(wrapper.find('WithSource')).toHaveLength(1); + expect(wrapper.find('FiltersGlobal')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx index 05a0b4441bb3a6..dc0b22c82af3ea 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx @@ -13,10 +13,7 @@ import { useHistory } from 'react-router-dom'; import { SecurityPageName } from '../../../app/types'; import { TimelineId } from '../../../../common/types/timeline'; import { GlobalTime } from '../../../common/containers/global_time'; -import { - indicesExistOrDataTemporarilyUnavailable, - WithSource, -} from '../../../common/containers/source'; +import { useWithSource } from '../../../common/containers/source'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { FiltersGlobal } from '../../../common/components/filters_global'; import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; @@ -82,6 +79,7 @@ export const DetectionEnginePageComponent: React.FC = ({ const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [ signalIndexName, ]); + const { indicesExist, indexPattern } = useWithSource('default', indexToAdd); if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { return ( @@ -104,77 +102,73 @@ export const DetectionEnginePageComponent: React.FC = ({ <> {hasEncryptionKey != null && !hasEncryptionKey && } {hasIndexWrite != null && !hasIndexWrite && } - - {({ indicesExist, indexPattern }) => { - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - - - - {i18n.LAST_ALERT} - {': '} - {lastAlerts} - - ) - } - title={i18n.PAGE_TITLE} - > - - {i18n.BUTTON_MANAGE_RULES} - - + {indicesExist ? ( + + + + + + + {i18n.LAST_ALERT} + {': '} + {lastAlerts} + + ) + } + title={i18n.PAGE_TITLE} + > + + {i18n.BUTTON_MANAGE_RULES} + + - - {({ to, from, deleteQuery, setQuery }) => ( - <> - <> - - - - - - )} - - - - ) : ( - - - - - ); - }} - + + {({ to, from, deleteQuery, setQuery }) => ( + <> + <> + + + + + + )} + + + + ) : ( + + + + + )} ); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx index df6ea65ba52ba5..0acb18082379ae 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.test.tsx @@ -12,10 +12,12 @@ import { TestProviders } from '../../../../../common/mock'; import { RuleDetailsPageComponent } from './index'; import { setAbsoluteRangeDatePicker } from '../../../../../common/store/inputs/actions'; import { useUserInfo } from '../../../../components/user_info'; +import { useWithSource } from '../../../../../common/containers/source'; import { useParams } from 'react-router-dom'; jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../components/user_info'); +jest.mock('../../../../../common/containers/source'); jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); @@ -30,6 +32,10 @@ describe('RuleDetailsPageComponent', () => { beforeAll(() => { (useUserInfo as jest.Mock).mockReturnValue({}); (useParams as jest.Mock).mockReturnValue({}); + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: {}, + }); }); it('renders correctly', () => { @@ -44,6 +50,6 @@ describe('RuleDetailsPageComponent', () => { } ); - expect(wrapper.find('WithSource')).toHaveLength(1); + expect(wrapper.find('GlobalTime')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx index 90fd4bb225ec5a..2ec603546983e4 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/details/index.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable react-hooks/rules-of-hooks */ -/* eslint-disable complexity */ +/* eslint-disable react-hooks/rules-of-hooks, complexity */ // TODO: Disabling complexity is temporary till this component is refactored as part of lists UI integration import { @@ -36,10 +35,7 @@ import { SiemSearchBar } from '../../../../../common/components/search_bar'; import { WrapperPage } from '../../../../../common/components/wrapper_page'; import { useRule } from '../../../../../alerts/containers/detection_engine/rules'; -import { - indicesExistOrDataTemporarilyUnavailable, - WithSource, -} from '../../../../../common/containers/source'; +import { useWithSource } from '../../../../../common/containers/source'; import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; @@ -255,6 +251,8 @@ export const RuleDetailsPageComponent: FC = ({ [history, ruleId] ); + const { indicesExist, indexPattern } = useWithSource('default', indexToAdd); + if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) { history.replace(getDetectionEngineUrl()); return null; @@ -264,187 +262,185 @@ export const RuleDetailsPageComponent: FC = ({ <> {hasIndexWrite != null && !hasIndexWrite && } {userHasNoPermissions(canUserCRUD) && } - - {({ indicesExist, indexPattern }) => { - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - {({ to, from, deleteQuery, setQuery }) => ( - - - - - - - - {detectionI18n.LAST_ALERT} - {': '} - {lastAlerts} - , - ] - : []), - , - ]} - title={title} - > - + {indicesExist ? ( + + {({ to, from, deleteQuery, setQuery }) => ( + + + + + + + + {detectionI18n.LAST_ALERT} + {': '} + {lastAlerts} + , + ] + : []), + , + ]} + title={title} + > + + + + + + + + + - - - + {ruleI18n.EDIT_RULE_SETTINGS} + - - - - - {ruleI18n.EDIT_RULE_SETTINGS} - - - - - - + - - {ruleError} - - - - - + + + + {ruleError} + + + + + - - - - - {defineRuleData != null && ( - - )} - - - - - - {scheduleRuleData != null && ( - - )} - - - + + + + + {defineRuleData != null && ( + + )} + + + + + + {scheduleRuleData != null && ( + + )} + + + + + {tabs} + + {ruleDetailTab === RuleDetailTabs.alerts && ( + <> + - {tabs} - - {ruleDetailTab === RuleDetailTabs.alerts && ( - <> - - - {ruleId != null && ( - - )} - - )} - {ruleDetailTab === RuleDetailTabs.exceptions && ( - )} - {ruleDetailTab === RuleDetailTabs.failures && } - - - )} - - ) : ( - - + + )} + {ruleDetailTab === RuleDetailTabs.exceptions && ( + + )} + {ruleDetailTab === RuleDetailTabs.failures && } + + + )} + + ) : ( + + - - - ); - }} - + + + )} ); }; +RuleDetailsPageComponent.displayName = 'RuleDetailsPageComponent'; + const makeMapStateToProps = () => { const getGlobalInputs = inputsSelectors.globalSelector(); return (state: State) => { @@ -467,3 +463,5 @@ const connector = connect(makeMapStateToProps, mapDispatchToProps); type PropsFromRedux = ConnectedProps; export const RuleDetailsPage = connector(memo(RuleDetailsPageComponent)); + +RuleDetailsPage.displayName = 'RuleDetailsPage'; diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index d8bdbd6e7ef5fb..03e48282cb754d 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -14,10 +14,7 @@ import { HeaderGlobal } from '../../common/components/header_global'; import { HelpMenu } from '../../common/components/help_menu'; import { AutoSaveWarningMsg } from '../../timelines/components/timeline/auto_save_warning'; import { UseUrlState } from '../../common/components/url_state'; -import { - WithSource, - indicesExistOrDataTemporarilyUnavailable, -} from '../../common/containers/source'; +import { useWithSource } from '../../common/containers/source'; import { useShowTimeline } from '../../common/utils/timeline/use_show_timeline'; import { navTabs } from './home_navigations'; @@ -60,31 +57,28 @@ export const HomePage: React.FC = ({ children }) => { ); const [showTimeline] = useShowTimeline(); + const { browserFields, indexPattern, indicesExist } = useWithSource(); return (
- - {({ browserFields, indexPattern, indicesExist }) => ( - - - {indicesExistOrDataTemporarilyUnavailable(indicesExist) && showTimeline && ( - <> - - - - )} - - {children} - + + + {indicesExist && showTimeline && ( + <> + + + )} - + + {children} +
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx index e60d876617dcaa..16207fcec3b26b 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx @@ -6,10 +6,9 @@ import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; -import { MockedProvider } from 'react-apollo/test-utils'; -import { mocksSource } from '../../containers/source/mock'; -import { wait } from '../../lib/helpers'; +import { useWithSource } from '../../containers/source'; +import { mockBrowserFields } from '../../containers/source/mock'; import { useKibana } from '../../lib/kibana'; import { TestProviders } from '../../mock'; import { createKibanaCoreStartMock } from '../../mock/kibana_core'; @@ -25,6 +24,14 @@ import { jest.mock('../link_to'); jest.mock('../../lib/kibana'); +jest.mock('../../containers/source', () => { + const original = jest.requireActual('../../containers/source'); + + return { + ...original, + useWithSource: jest.fn(), + }; +}); jest.mock('uuid', () => { return { @@ -52,6 +59,9 @@ describe('DraggableWrapperHoverContent', () => { beforeAll(() => { // our mock implementation of the useAddToTimeline hook returns a mock startDragToTimeline function: (useAddToTimeline as jest.Mock).mockReturnValue(jest.fn()); + (useWithSource as jest.Mock).mockReturnValue({ + browserFields: mockBrowserFields, + }); }); // Suppress warnings about "react-beautiful-dnd" @@ -323,17 +333,15 @@ describe('DraggableWrapperHoverContent', () => { test(`it ${assertion} the 'Add to timeline investigation' button when showTopN is ${showTopN}, value is ${maybeValue}, and a draggableId is ${maybeDraggableId}`, () => { const wrapper = mount( - - - + ); @@ -348,15 +356,13 @@ describe('DraggableWrapperHoverContent', () => { test('when clicked, it invokes the `startDragToTimeline` function returned by the `useAddToTimeline` hook', () => { const wrapper = mount( - - - + ); @@ -380,18 +386,15 @@ describe('DraggableWrapperHoverContent', () => { const aggregatableStringField = 'cloud.account.id'; const wrapper = mount( - - - + ); - await wait(); // https://github.com/apollographql/react-apollo/issues/1711 wrapper.update(); expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true); @@ -401,18 +404,15 @@ describe('DraggableWrapperHoverContent', () => { const whitelistedField = 'signal.rule.name'; const wrapper = mount( - - - + ); - await wait(); wrapper.update(); expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true); @@ -422,18 +422,15 @@ describe('DraggableWrapperHoverContent', () => { const notKnownToBrowserFields = 'unknown.field'; const wrapper = mount( - - - + ); - await wait(); wrapper.update(); expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false); @@ -443,18 +440,15 @@ describe('DraggableWrapperHoverContent', () => { const whitelistedField = 'signal.rule.name'; const wrapper = mount( - - - + ); - await wait(); wrapper.update(); wrapper.find('[data-test-subj="show-top-field"]').first().simulate('click'); @@ -467,18 +461,15 @@ describe('DraggableWrapperHoverContent', () => { const whitelistedField = 'signal.rule.name'; const wrapper = mount( - - - + ); - await wait(); wrapper.update(); expect(wrapper.find('[data-test-subj="eventsByDatasetOverviewPanel"]').first().exists()).toBe( @@ -490,19 +481,16 @@ describe('DraggableWrapperHoverContent', () => { const whitelistedField = 'signal.rule.name'; const wrapper = mount( - - - + ); - await wait(); wrapper.update(); expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false); @@ -512,19 +500,16 @@ describe('DraggableWrapperHoverContent', () => { const whitelistedField = 'signal.rule.name'; const wrapper = mount( - - - + ); - await wait(); wrapper.update(); expect( diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx index f916f42fe41cdc..e805750cf24776 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx @@ -8,7 +8,7 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { DraggableId } from 'react-beautiful-dnd'; -import { getAllFieldsByName, WithSource } from '../../containers/source'; +import { getAllFieldsByName, useWithSource } from '../../containers/source'; import { useAddToTimeline } from '../../hooks/use_add_to_timeline'; import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard'; import { useKibana } from '../../lib/kibana'; @@ -79,6 +79,8 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [field, value, filterManager, onFilterAdded]); + const { browserFields } = useWithSource(); + return ( <> {!showTopN && value != null && ( @@ -117,40 +119,36 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ )} - - {({ browserFields }) => ( + <> + {allowTopN({ + browserField: getAllFieldsByName(browserFields)[field], + fieldName: field, + }) && ( <> - {allowTopN({ - browserField: getAllFieldsByName(browserFields)[field], - fieldName: field, - }) && ( - <> - {!showTopN && ( - - - - )} - - {showTopN && ( - - )} - + {!showTopN && ( + + + + )} + + {showTopN && ( + )} )} - + {!showTopN && ( diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx index de19c1903586ae..17fdf2163b58ed 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx @@ -16,7 +16,7 @@ import { getAppOverviewUrl } from '../link_to'; import { MlPopover } from '../ml_popover/ml_popover'; import { SiemNavigation } from '../navigation'; import * as i18n from './translations'; -import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; +import { useWithSource } from '../../containers/source'; import { useGetUrlSearch } from '../navigation/use_get_url_search'; import { useKibana } from '../../lib/kibana'; import { APP_ID, ADD_DATA_PATH, APP_ALERTS_PATH } from '../../../../common/constants'; @@ -41,6 +41,7 @@ interface HeaderGlobalProps { hideDetectionEngine?: boolean; } export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => { + const { indicesExist } = useWithSource(); const search = useGetUrlSearch(navTabs.overview); const { navigateToApp } = useKibana().services.application; const goToOverview = useCallback( @@ -54,60 +55,55 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine return ( - - {({ indicesExist }) => ( - <> - - - - - - - + <> + + + + + + + - - {indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - key !== SecurityPageName.alerts, navTabs) - : navTabs - } - /> - ) : ( - key === SecurityPageName.overview, navTabs)} - /> - )} - - + + {indicesExist ? ( + key !== SecurityPageName.alerts, navTabs) + : navTabs + } + /> + ) : ( + key === SecurityPageName.overview, navTabs)} + /> + )} + + - - - {indicesExistOrDataTemporarilyUnavailable(indicesExist) && - window.location.pathname.includes(APP_ALERTS_PATH) && ( - - - - )} + + + {indicesExist && window.location.pathname.includes(APP_ALERTS_PATH) && ( + + + + )} - - - {i18n.BUTTON_ADD_DATA} - - - + + + {i18n.BUTTON_ADD_DATA} + - - )} - + + + ); diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx index c28f5ab8aa44f0..09da027569c61a 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx @@ -8,7 +8,7 @@ import React, { useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { GlobalTime } from '../../containers/global_time'; -import { BrowserFields, WithSource } from '../../containers/source'; +import { BrowserFields, useWithSource } from '../../containers/source'; import { useKibana } from '../../lib/kibana'; import { esQuery, Filter, Query } from '../../../../../../../src/plugins/data/public'; import { inputsModel, inputsSelectors, State } from '../../store'; @@ -99,7 +99,7 @@ const StatefulTopNComponent: React.FC = ({ // * `id` (`timelineId`) may only be populated when we are rendered in the // context of the active timeline. // * `indexToAdd`, which enables the alerts index to be appended to - // the `indexPattern` returned by `WithSource`, may only be populated when + // the `indexPattern` returned by `useWithSource`, may only be populated when // this component is rendered in the context of the active timeline. This // behavior enables the 'All events' view by appending the alerts index // to the index pattern. @@ -117,54 +117,50 @@ const StatefulTopNComponent: React.FC = ({ timelineId === ACTIVE_TIMELINE_REDUX_ID ? activeTimelineEventType : undefined ); + const { indexPattern } = useWithSource('default', indexToAdd); + return ( {({ from, deleteQuery, setQuery, to }) => ( - - {({ indexPattern }) => ( - - )} - + )} ); diff --git a/x-pack/plugins/security_solution/public/common/containers/global_time/index.tsx b/x-pack/plugins/security_solution/public/common/containers/global_time/index.tsx index 9b9b5c5d815b99..9c9778c7074ee1 100644 --- a/x-pack/plugins/security_solution/public/common/containers/global_time/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/global_time/index.tsx @@ -94,3 +94,5 @@ export const connector = connect(mapStateToProps, mapDispatchToProps); type PropsFromRedux = ConnectedProps; export const GlobalTime = connector(React.memo(GlobalTimeComponent)); + +GlobalTime.displayName = 'GlobalTime'; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx index d1a183a402e371..c30c3668638a3f 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx @@ -4,55 +4,48 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; -import { mount } from 'enzyme'; -import React from 'react'; -import { MockedProvider } from 'react-apollo/test-utils'; +import { act, renderHook } from '@testing-library/react-hooks'; -import { wait } from '../../lib/helpers'; - -import { WithSource, indicesExistOrDataTemporarilyUnavailable } from '.'; +import { useWithSource, indicesExistOrDataTemporarilyUnavailable } from '.'; import { mockBrowserFields, mockIndexFields, mocksSource } from './mock'; jest.mock('../../lib/kibana'); +jest.mock('../../utils/apollo_context', () => ({ + useApolloClient: jest.fn().mockReturnValue({ + query: jest.fn().mockImplementation(() => Promise.resolve(mocksSource[0].result)), + }), +})); describe('Index Fields & Browser Fields', () => { - test('Index Fields', async () => { - mount( - - - {({ indexPattern }) => { - if (!isEqual(indexPattern.fields, [])) { - expect(indexPattern.fields).toEqual(mockIndexFields); - } + test('returns memoized value', async () => { + const { result, waitForNextUpdate, rerender } = renderHook(() => useWithSource()); + await waitForNextUpdate(); - return null; - }} - - - ); + const result1 = result.current; + act(() => rerender()); + const result2 = result.current; - // Why => https://github.com/apollographql/react-apollo/issues/1711 - await wait(); + return expect(result1).toBe(result2); }); - test('Browser Fields', async () => { - mount( - - - {({ browserFields }) => { - if (!isEqual(browserFields, {})) { - expect(browserFields).toEqual(mockBrowserFields); - } + test('Index Fields', async () => { + const { result, waitForNextUpdate } = renderHook(() => useWithSource()); - return null; - }} - - - ); + await waitForNextUpdate(); - // Why => https://github.com/apollographql/react-apollo/issues/1711 - await wait(); + return expect(result).toEqual({ + current: { + indicesExist: true, + browserFields: mockBrowserFields, + indexPattern: { + fields: mockIndexFields, + title: 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,packetbeat-*,winlogbeat-*', + }, + loading: false, + errorMessage: null, + }, + error: undefined, + }); }); describe('indicesExistOrDataTemporarilyUnavailable', () => { diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index ad480ad2c496bf..34ac5f8f5d94fa 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -6,8 +6,7 @@ import { isUndefined } from 'lodash'; import { get, keyBy, pick, set, isEmpty } from 'lodash/fp'; -import { Query } from 'react-apollo'; -import React, { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import memoizeOne from 'memoize-one'; import { IIndexPattern } from 'src/plugins/data/public'; @@ -50,18 +49,6 @@ export const getAllFieldsByName = ( ): { [fieldName: string]: Partial } => keyBy('name', getAllBrowserFields(browserFields)); -interface WithSourceArgs { - indicesExist: boolean; - browserFields: BrowserFields; - indexPattern: IIndexPattern; -} - -interface WithSourceProps { - children: (args: WithSourceArgs) => React.ReactNode; - indexToAdd?: string[] | null; - sourceId: string; -} - export const getIndexFields = memoizeOne( (title: string, fields: IndexField[]): IIndexPattern => fields && fields.length > 0 @@ -71,7 +58,8 @@ export const getIndexFields = memoizeOne( ), title, } - : { fields: [], title } + : { fields: [], title }, + (newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length ); export const getBrowserFields = memoizeOne( @@ -82,10 +70,26 @@ export const getBrowserFields = memoizeOne( set([field.category, 'fields', field.name], field, accumulator), {} ) - : {} + : {}, + // Update the value only if _title has changed + (newArgs, lastArgs) => newArgs[0] === lastArgs[0] ); -export const WithSource = React.memo(({ children, indexToAdd, sourceId }) => { +export const indicesExistOrDataTemporarilyUnavailable = ( + indicesExist: boolean | null | undefined +) => indicesExist || isUndefined(indicesExist); + +const EMPTY_BROWSER_FIELDS = {}; + +interface UseWithSourceState { + browserFields: BrowserFields; + errorMessage: string | null; + indexPattern: IIndexPattern; + indicesExist: boolean | undefined | null; + loading: boolean; +} + +export const useWithSource = (sourceId = 'default', indexToAdd?: string[] | null) => { const [configIndex] = useUiSetting$(DEFAULT_INDEX_KEY); const defaultIndex = useMemo(() => { if (indexToAdd != null && !isEmpty(indexToAdd)) { @@ -94,87 +98,84 @@ export const WithSource = React.memo(({ children, indexToAdd, s return configIndex; }, [configIndex, indexToAdd]); - return ( - - query={sourceQuery} - fetchPolicy="cache-first" - notifyOnNetworkStatusChange - variables={{ - sourceId, - defaultIndex, - }} - > - {({ data }) => - children({ - indicesExist: get('source.status.indicesExist', data), - browserFields: getBrowserFields( - defaultIndex.join(), - get('source.status.indexFields', data) - ), - indexPattern: getIndexFields(defaultIndex.join(), get('source.status.indexFields', data)), - }) - } - - ); -}); + const [state, setState] = useState({ + browserFields: EMPTY_BROWSER_FIELDS, + errorMessage: null, + indexPattern: getIndexFields(defaultIndex.join(), []), + indicesExist: undefined, + loading: false, + }); -WithSource.displayName = 'WithSource'; + const apolloClient = useApolloClient(); -export const indicesExistOrDataTemporarilyUnavailable = (indicesExist: boolean | undefined) => - indicesExist || isUndefined(indicesExist); + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); -export const useWithSource = (sourceId: string, indices: string[]) => { - const [loading, updateLoading] = useState(false); - const [indicesExist, setIndicesExist] = useState(undefined); - const [browserFields, setBrowserFields] = useState(null); - const [indexPattern, setIndexPattern] = useState(null); - const [errorMessage, updateErrorMessage] = useState(null); + async function fetchSource() { + if (!apolloClient) return; - const apolloClient = useApolloClient(); - async function fetchSource(signal: AbortSignal) { - updateLoading(true); - if (apolloClient) { - apolloClient - .query({ + setState((prevState) => ({ ...prevState, loading: true })); + + try { + const result = await apolloClient.query({ query: sourceQuery, fetchPolicy: 'cache-first', variables: { sourceId, - defaultIndex: indices, + defaultIndex, }, context: { fetchOptions: { - signal, + signal: abortCtrl.signal, }, }, - }) - .then( - (result) => { - updateLoading(false); - updateErrorMessage(null); - setIndicesExist(get('data.source.status.indicesExist', result)); - setBrowserFields( - getBrowserFields(indices.join(), get('data.source.status.indexFields', result)) - ); - setIndexPattern( - getIndexFields(indices.join(), get('data.source.status.indexFields', result)) - ); - }, - (error) => { - updateLoading(false); - updateErrorMessage(error.message); - } - ); + }); + if (!isSubscribed) { + return setState((prevState) => ({ + ...prevState, + loading: false, + })); + } + + setState({ + loading: false, + indicesExist: indicesExistOrDataTemporarilyUnavailable( + get('data.source.status.indicesExist', result) + ), + browserFields: getBrowserFields( + defaultIndex.join(), + get('data.source.status.indexFields', result) + ), + indexPattern: getIndexFields( + defaultIndex.join(), + get('data.source.status.indexFields', result) + ), + errorMessage: null, + }); + } catch (error) { + if (!isSubscribed) { + return setState((prevState) => ({ + ...prevState, + loading: false, + })); + } + + setState((prevState) => ({ + ...prevState, + loading: false, + errorMessage: error.message, + })); + } } - } - useEffect(() => { - const abortCtrl = new AbortController(); - const signal = abortCtrl.signal; - fetchSource(signal); - return () => abortCtrl.abort(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [apolloClient, sourceId, indices]); + fetchSource(); + + return () => { + isSubscribed = false; + return abortCtrl.abort(); + }; + }, [apolloClient, sourceId, defaultIndex]); - return { indicesExist, browserFields, indexPattern, loading, errorMessage }; + return state; }; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx index 936789625a4dde..e520facf285c2b 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { IIndexPattern } from 'src/plugins/data/public'; import { MemoryRouter } from 'react-router-dom'; import useResizeObserver from 'use-resize-observer/polyfilled'; @@ -19,12 +18,7 @@ import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { getHostDetailsPageFilters } from './helpers'; jest.mock('../../../common/containers/source', () => ({ - indicesExistOrDataTemporarilyUnavailable: () => true, - WithSource: ({ - children, - }: { - children: (args: { indicesExist: boolean; indexPattern: IIndexPattern }) => React.ReactNode; - }) => children({ indicesExist: true, indexPattern: mockIndexPattern }), + useWithSource: jest.fn().mockReturnValue({ indicesExist: true, indexPattern: mockIndexPattern }), })); // Test will fail because we will to need to mock some core services to make the test work diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index e3f00a377d2724..1c66a9edc19475 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -27,10 +27,7 @@ import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; import { HostOverviewByNameQuery } from '../../containers/hosts/overview'; import { KpiHostDetailsQuery } from '../../containers/kpi_host_details'; -import { - indicesExistOrDataTemporarilyUnavailable, - WithSource, -} from '../../../common/containers/source'; +import { useWithSource } from '../../../common/containers/source'; import { LastEventIndexKey } from '../../../graphql/types'; import { useKibana } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; @@ -83,132 +80,126 @@ const HostDetailsComponent = React.memo( }, [setAbsoluteRangeDatePicker] ); + const { indicesExist, indexPattern } = useWithSource(); + const filterQuery = convertToBuildEsQuery({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + indexPattern, + queries: [query], + filters: getFilters(), + }); return ( <> - - {({ indicesExist, indexPattern }) => { - const filterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(kibana.services.uiSettings), - indexPattern, - queries: [query], - filters: getFilters(), - }); - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - - - - - } - title={detailName} - /> - - + + + + + + + } + title={detailName} + /> + + + {({ hostOverview, loading, id, inspect, refetch }) => ( + - {({ hostOverview, loading, id, inspect, refetch }) => ( - - {({ isLoadingAnomaliesData, anomaliesData }) => ( - { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }} - /> - )} - - )} - - - - - - {({ kpiHostDetails, id, inspect, loading, refetch }) => ( - ( + { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }} /> )} - - - - - - - - - + )} + + + + + + {({ kpiHostDetails, id, inspect, loading, refetch }) => ( + - - - ) : ( - - - - - - ); - }} - + )} + + + + + + + + + + + + ) : ( + + + + + + )} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx index 85db3b4e159f12..ea0b32137eb395 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx @@ -5,15 +5,12 @@ */ import { mount } from 'enzyme'; -import { cloneDeep } from 'lodash/fp'; import React from 'react'; import { Router } from 'react-router-dom'; -import { MockedProvider } from 'react-apollo/test-utils'; import { Filter } from '../../../../../../src/plugins/data/common/es_query'; import '../../common/mock/match_media'; -import { mocksSource } from '../../common/containers/source/mock'; -import { wait } from '../../common/lib/helpers'; +import { useWithSource } from '../../common/containers/source'; import { apolloClientObservable, TestProviders, @@ -28,6 +25,8 @@ import { HostsComponentProps } from './types'; import { Hosts } from './hosts'; import { HostsTabs } from './hosts_tabs'; +jest.mock('../../common/containers/source'); + // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar jest.mock('../../common/components/search_bar', () => ({ @@ -37,19 +36,6 @@ jest.mock('../../common/components/query_bar', () => ({ QueryBar: () => null, })); -let localSource: Array<{ - request: {}; - result: { - data: { - source: { - status: { - indicesExist: boolean; - }; - }; - }; - }; -}>; - type Action = 'PUSH' | 'POP' | 'REPLACE'; const pop: Action = 'POP'; const location = { @@ -84,57 +70,49 @@ describe('Hosts - rendering', () => { hostsPagePath: '', }; - beforeEach(() => { - localSource = cloneDeep(mocksSource); - }); - test('it renders the Setup Instructions text when no index is available', async () => { - localSource[0].result.data.source.status.indicesExist = false; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: false, + }); + const wrapper = mount( - - - - - + + + ); - // Why => https://github.com/apollographql/react-apollo/issues/1711 - await new Promise((resolve) => setTimeout(resolve)); - wrapper.update(); expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(true); }); test('it DOES NOT render the Setup Instructions text when an index is available', async () => { - localSource[0].result.data.source.status.indicesExist = true; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: {}, + }); const wrapper = mount( - - - - - + + + ); - // Why => https://github.com/apollographql/react-apollo/issues/1711 - await new Promise((resolve) => setTimeout(resolve)); - wrapper.update(); expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(false); }); test('it should render tab navigation', async () => { - localSource[0].result.data.source.status.indicesExist = true; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: {}, + }); + const wrapper = mount( - - - - - + + + ); - await wait(); - wrapper.update(); expect(wrapper.find(SiemNavigation).exists()).toBe(true); }); @@ -170,22 +148,21 @@ describe('Hosts - rendering', () => { }, }, ]; - localSource[0].result.data.source.status.indicesExist = true; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: { fields: [], title: 'title' }, + }); const myState: State = mockGlobalState; const { storage } = createSecuritySolutionStorageMock(); const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const wrapper = mount( - - - - - + + + ); - await wait(); wrapper.update(); - myStore.dispatch(inputsActions.setSearchBarFilter({ id: 'global', filters: newFilters })); wrapper.update(); expect(wrapper.find(HostsTabs).props().filterQuery).toEqual( diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index f6429544f855e0..f5cc651a30443d 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -22,10 +22,7 @@ import { manageQuery } from '../../common/components/page/manage_query'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { KpiHostsQuery } from '../containers/kpi_hosts'; -import { - indicesExistOrDataTemporarilyUnavailable, - WithSource, -} from '../../common/containers/source'; +import { useWithSource } from '../../common/containers/source'; import { LastEventIndexKey } from '../../graphql/types'; import { useKibana } from '../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../common/lib/keury'; @@ -77,87 +74,84 @@ export const HostsComponent = React.memo( }, [setAbsoluteRangeDatePicker] ); + const { indicesExist, indexPattern } = useWithSource(); + const filterQuery = convertToBuildEsQuery({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + indexPattern, + queries: [query], + filters, + }); + const tabsFilterQuery = convertToBuildEsQuery({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + indexPattern, + queries: [query], + filters: tabsFilters, + }); return ( <> - - {({ indicesExist, indexPattern }) => { - const filterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(kibana.services.uiSettings), - indexPattern, - queries: [query], - filters, - }); - const tabsFilterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(kibana.services.uiSettings), - indexPattern, - queries: [query], - filters: tabsFilters, - }); - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - - - - } - title={i18n.PAGE_TITLE} - /> - - - {({ kpiHosts, loading, id, inspect, refetch }) => ( - - )} - - - - - - - - - + + + + + + } + title={i18n.PAGE_TITLE} + /> + + + {({ kpiHosts, loading, id, inspect, refetch }) => ( + - - - ) : ( - - - - - - ); - }} - + )} + + + + + + + + + + + + ) : ( + + + + + + )} diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/pages/ip_details/__snapshots__/index.test.tsx.snap index 6e76ff00a8141c..d7af8d6910f45c 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/__snapshots__/index.test.tsx.snap @@ -1,15 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Ip Details it matches the snapshot 1`] = ` - - - - +
+ + + + - +
`; diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx index bbb964ae17b9f0..a87eb3d0574479 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx @@ -5,15 +5,13 @@ */ import { shallow } from 'enzyme'; -import { cloneDeep } from 'lodash/fp'; import React from 'react'; import { Router } from 'react-router-dom'; -import { MockedProvider } from 'react-apollo/test-utils'; import { ActionCreator } from 'typescript-fsa'; import '../../../common/mock/match_media'; -import { mocksSource } from '../../../common/containers/source/mock'; +import { useWithSource } from '../../../common/containers/source'; import { FlowTarget } from '../../../graphql/types'; import { apolloClientObservable, @@ -32,6 +30,9 @@ const pop: Action = 'POP'; type GlobalWithFetch = NodeJS.Global & { fetch: jest.Mock }; +jest.mock('../../../common/lib/kibana'); +jest.mock('../../../common/containers/source'); + // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar jest.mock('../../../common/components/search_bar', () => ({ @@ -41,19 +42,6 @@ jest.mock('../../../common/components/query_bar', () => ({ QueryBar: () => null, })); -let localSource: Array<{ - request: {}; - result: { - data: { - source: { - status: { - indicesExist: boolean; - }; - }; - }; - }; -}>; - const getMockHistory = (ip: string) => ({ length: 2, location: { @@ -104,6 +92,10 @@ describe('Ip Details', () => { const mount = useMountAppended(); beforeAll(() => { + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: false, + indexPattern: {}, + }); (global as GlobalWithFetch).fetch = jest.fn().mockImplementationOnce(() => Promise.resolve({ ok: true, @@ -124,7 +116,6 @@ describe('Ip Details', () => { beforeEach(() => { store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); - localSource = cloneDeep(mocksSource); }); test('it renders', () => { @@ -138,20 +129,18 @@ describe('Ip Details', () => { }); test('it renders ipv6 headline', async () => { - localSource[0].result.data.source.status.indicesExist = true; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: {}, + }); const ip = 'fe80--24ce-f7ff-fede-a571'; const wrapper = mount( - - - - - + + + ); - // Why => https://github.com/apollographql/react-apollo/issues/1711 - await new Promise((resolve) => setTimeout(resolve)); - wrapper.update(); expect( wrapper .find('[data-test-subj="ip-details-headline"] [data-test-subj="header-page-title"]') diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx index face3f8904794e..162b3a7c158d5e 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.tsx @@ -22,10 +22,7 @@ import { IpOverview } from '../../components/ip_overview'; import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; import { IpOverviewQuery } from '../../containers/ip_overview'; -import { - indicesExistOrDataTemporarilyUnavailable, - WithSource, -} from '../../../common/containers/source'; +import { useWithSource } from '../../../common/containers/source'; import { FlowTargetSourceDest, LastEventIndexKey } from '../../../graphql/types'; import { useKibana } from '../../../common/lib/kibana'; import { decodeIpv6 } from '../../../common/lib/helpers'; @@ -74,208 +71,207 @@ export const IPDetailsComponent: React.FC { setIpDetailsTablesActivePageToZero(); }, [detailName, setIpDetailsTablesActivePageToZero]); - return ( - <> - - {({ indicesExist, indexPattern }) => { - const ip = decodeIpv6(detailName); - const filterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(kibana.services.uiSettings), - indexPattern, - queries: [query], - filters, - }); + const { indicesExist, indexPattern } = useWithSource(); + const ip = decodeIpv6(detailName); + const filterQuery = convertToBuildEsQuery({ + config: esQuery.getEsQueryConfig(uiSettings), + indexPattern, + queries: [query], + filters, + }); - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - + return ( +
+ {indicesExist ? ( + + + + - - } - title={ip} - > - - + + } + title={ip} + > + + - + {({ id, inspect, ipOverviewData, loading, refetch }) => ( + - {({ id, inspect, ipOverviewData, loading, refetch }) => ( - - {({ isLoadingAnomaliesData, anomaliesData }) => ( - - )} - - )} - - - - - - - ( + - - - - - - - - - - - - - - - - - - + )} + + )} + - + - + + + - - - + + + - + - + + + - - - + - - - ) : ( - - + + + + + + + + + + + + + + + + + + + + + ) : ( + + - - - ); - }} - + + + )} - +
); }; IPDetailsComponent.displayName = 'IPDetailsComponent'; diff --git a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx index e1078dee3eb0d7..7cdfdbf0af69a6 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx @@ -5,14 +5,12 @@ */ import { mount } from 'enzyme'; -import { cloneDeep } from 'lodash/fp'; import React from 'react'; import { Router } from 'react-router-dom'; -import { MockedProvider } from 'react-apollo/test-utils'; import '../../common/mock/match_media'; import { Filter } from '../../../../../../src/plugins/data/common/es_query'; -import { mocksSource } from '../../common/containers/source/mock'; +import { useWithSource } from '../../common/containers/source'; import { TestProviders, mockGlobalState, @@ -26,6 +24,8 @@ import { inputsActions } from '../../common/store/inputs'; import { Network } from './network'; import { NetworkRoutes } from './navigation'; +jest.mock('../../common/containers/source'); + // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar jest.mock('../../common/components/search_bar', () => ({ @@ -35,19 +35,6 @@ jest.mock('../../common/components/query_bar', () => ({ QueryBar: () => null, })); -let localSource: Array<{ - request: {}; - result: { - data: { - source: { - status: { - indicesExist: boolean; - }; - }; - }; - }; -}>; - type Action = 'PUSH' | 'POP' | 'REPLACE'; const pop: Action = 'POP'; const location = { @@ -84,41 +71,33 @@ const getMockProps = () => ({ }); describe('rendering - rendering', () => { - beforeEach(() => { - localSource = cloneDeep(mocksSource); - }); - test('it renders the Setup Instructions text when no index is available', async () => { - localSource[0].result.data.source.status.indicesExist = false; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: false, + }); + const wrapper = mount( - - - - - + + + ); - // Why => https://github.com/apollographql/react-apollo/issues/1711 - await new Promise((resolve) => setTimeout(resolve)); - wrapper.update(); expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(true); }); test('it DOES NOT render the Setup Instructions text when an index is available', async () => { - localSource[0].result.data.source.status.indicesExist = true; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: {}, + }); const wrapper = mount( - - - - - + + + ); - // Why => https://github.com/apollographql/react-apollo/issues/1711 - await new Promise((resolve) => setTimeout(resolve)); - wrapper.update(); expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(false); }); @@ -154,20 +133,20 @@ describe('rendering - rendering', () => { }, }, ]; - localSource[0].result.data.source.status.indicesExist = true; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: { fields: [], title: 'title' }, + }); const myState: State = mockGlobalState; const { storage } = createSecuritySolutionStorageMock(); const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const wrapper = mount( - - - - - + + + ); - await new Promise((resolve) => setTimeout(resolve)); wrapper.update(); myStore.dispatch(inputsActions.setSearchBarFilter({ id: 'global', filters: newFilters })); diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index 845a6bbd95dd6d..4275c1641f5176 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -23,10 +23,7 @@ import { KpiNetworkComponent } from '..//components/kpi_network'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { KpiNetworkQuery } from '../../network/containers/kpi_network'; -import { - indicesExistOrDataTemporarilyUnavailable, - WithSource, -} from '../../common/containers/source'; +import { useWithSource } from '../../common/containers/source'; import { LastEventIndexKey } from '../../graphql/types'; import { useKibana } from '../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../common/lib/keury'; @@ -78,103 +75,100 @@ const NetworkComponent = React.memo( [setAbsoluteRangeDatePicker] ); + const { indicesExist, indexPattern } = useWithSource(sourceId); + const filterQuery = convertToBuildEsQuery({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + indexPattern, + queries: [query], + filters, + }); + const tabsFilterQuery = convertToBuildEsQuery({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + indexPattern, + queries: [query], + filters: tabsFilters, + }); + return ( <> - - {({ indicesExist, indexPattern }) => { - const filterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(kibana.services.uiSettings), - indexPattern, - queries: [query], - filters, - }); - const tabsFilterQuery = convertToBuildEsQuery({ - config: esQuery.getEsQueryConfig(kibana.services.uiSettings), - indexPattern, - queries: [query], - filters: tabsFilters, - }); - - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - - - - } - title={i18n.PAGE_TITLE} - /> - - + + + + + + } + title={i18n.PAGE_TITLE} + /> + + + + + + + {({ kpiNetwork, loading, id, inspect, refetch }) => ( + + )} + + {capabilitiesFetched && !isInitializing ? ( + <> - - {({ kpiNetwork, loading, id, inspect, refetch }) => ( - - )} - - - {capabilitiesFetched && !isInitializing ? ( - <> - - - - - - - - - ) : ( - - )} + - - - ) : ( - - - - - ); - }} - + + + + ) : ( + + )} + + + +
+ ) : ( + + + + + )} diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx index a2010f1f64b718..d6e8fb984ac0ff 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx @@ -5,17 +5,16 @@ */ import { mount } from 'enzyme'; -import { cloneDeep } from 'lodash/fp'; import React from 'react'; -import { MockedProvider } from 'react-apollo/test-utils'; import { MemoryRouter } from 'react-router-dom'; import '../../common/mock/match_media'; import { TestProviders } from '../../common/mock'; -import { mocksSource } from '../../common/containers/source/mock'; +import { useWithSource } from '../../common/containers/source'; import { Overview } from './index'; jest.mock('../../common/lib/kibana'); +jest.mock('../../common/containers/source'); // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -26,56 +25,36 @@ jest.mock('../../common/components/query_bar', () => ({ QueryBar: () => null, })); -let localSource: Array<{ - request: {}; - result: { - data: { - source: { - status: { - indicesExist: boolean; - }; - }; - }; - }; -}>; - describe('Overview', () => { describe('rendering', () => { - beforeEach(() => { - localSource = cloneDeep(mocksSource); - }); - test('it renders the Setup Instructions text when no index is available', async () => { - localSource[0].result.data.source.status.indicesExist = false; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: false, + }); + const wrapper = mount( - - - - - + + + ); - // Why => https://github.com/apollographql/react-apollo/issues/1711 - await new Promise((resolve) => setTimeout(resolve)); - wrapper.update(); + expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(true); }); test('it DOES NOT render the Getting started text when an index is available', async () => { - localSource[0].result.data.source.status.indicesExist = true; + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: {}, + }); const wrapper = mount( - - - - - + + + ); - // Why => https://github.com/apollographql/react-apollo/issues/1711 - await new Promise((resolve) => setTimeout(resolve)); - wrapper.update(); expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(false); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 543dafd50c8e03..53cb32a16a9de1 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -16,10 +16,7 @@ import { FiltersGlobal } from '../../common/components/filters_global'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { GlobalTime } from '../../common/containers/global_time'; -import { - WithSource, - indicesExistOrDataTemporarilyUnavailable, -} from '../../common/containers/source'; +import { useWithSource } from '../../common/containers/source'; import { EventsByDataset } from '../components/events_by_dataset'; import { EventCounts } from '../components/event_counts'; import { OverviewEmpty } from '../components/overview_empty'; @@ -41,89 +38,89 @@ const OverviewComponent: React.FC = ({ filters = NO_FILTERS, query = DEFAULT_QUERY, setAbsoluteRangeDatePicker, -}) => ( - <> - - {({ indicesExist, indexPattern }) => - indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - - - - - - - - - - - {({ from, deleteQuery, setQuery, to }) => ( - - - - - - - - - - - - - - - - - - - )} - - - - - - ) : ( - - ) - } - - - - -); +}) => { + const { indicesExist, indexPattern } = useWithSource(); + + return ( + <> + {indicesExist ? ( + + + + + + + + + + + + + + {({ from, deleteQuery, setQuery, to }) => ( + + + + + + + + + + + + + + + + + + + )} + + + + + + ) : ( + + )} + + + + ); +}; const makeMapStateToProps = () => { const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/button/index.tsx index ae05d99b58ee08..a1392ad8b82707 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/button/index.tsx @@ -10,7 +10,7 @@ import { rgba } from 'polished'; import React, { useMemo } from 'react'; import styled from 'styled-components'; -import { WithSource } from '../../../../common/containers/source'; +import { useWithSource } from '../../../../common/containers/source'; import { IS_DRAGGING_CLASS_NAME } from '../../../../common/components/drag_and_drop/helpers'; import { DataProvider } from '../../timeline/data_providers/data_provider'; import { flattenIntoAndGroups } from '../../timeline/data_providers/helpers'; @@ -84,6 +84,7 @@ interface FlyoutButtonProps { export const FlyoutButton = React.memo( ({ onOpen, show, dataProviders, timelineId }) => { const badgeCount = useMemo(() => getBadgeCount(dataProviders), [dataProviders]); + const { browserFields } = useWithSource(); if (!show) { return null; @@ -121,19 +122,15 @@ export const FlyoutButton = React.memo( - - {({ browserFields }) => ( - - )} - + ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index 51cfe8ae33b05e..df76eb350ace7f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -9,7 +9,7 @@ import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { NO_ALERT_INDEX } from '../../../../common/constants'; -import { WithSource } from '../../../common/containers/source'; +import { useWithSource } from '../../../common/containers/source'; import { useSignalIndex } from '../../../alerts/containers/detection_engine/alerts/use_signal_index'; import { inputsModel, inputsSelectors, State } from '../../../common/store'; import { timelineActions, timelineSelectors } from '../../store/timeline'; @@ -158,40 +158,38 @@ const StatefulTimelineComponent = React.memo( // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const { indexPattern, browserFields } = useWithSource('default', indexToAdd); + return ( - - {({ indexPattern, browserFields }) => ( - - )} - + ); }, (prevProps, nextProps) => {