From 802f32739eefa2539b8f0fa08f0f0c6fbbe9ac9d Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 16 Sep 2019 22:24:35 -0400 Subject: [PATCH] [SIEM] Do not update state component when they did unmount (#45847) * do not update state component when they did unmount * Adds signals to the fetches * review I --- .../components/embeddables/embedded_map.tsx | 84 +++++++++------- .../components/last_event_time/index.test.tsx | 96 ++++++++----------- .../components/last_event_time/index.tsx | 87 ++++++++--------- .../ml/anomaly/use_anomalies_table_data.ts | 88 +++++++++-------- .../components/ml/api/anomalies_table_data.ts | 4 +- .../components/ml/api/get_ml_capabilities.ts | 4 +- .../permissions/ml_capabilities_provider.tsx | 40 +++++--- .../siem/public/components/ml_popover/api.tsx | 14 ++- .../ml_popover/hooks/use_index_patterns.tsx | 44 ++++++--- .../ml_popover/hooks/use_job_summary_data.tsx | 52 ++++++---- .../ml_popover/hooks/use_siem_jobs.tsx | 49 ++++++---- .../events/last_event_time/index.ts | 76 ++++++++------- .../containers/hosts/first_last_seen/index.ts | 22 +++-- 13 files changed, 370 insertions(+), 290 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx index dafaaad01cdb1..370dafa4a384b 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx @@ -53,49 +53,59 @@ export const EmbeddedMap = React.memo( const [loadingKibanaIndexPatterns, kibanaIndexPatterns] = useIndexPatterns(); const [siemDefaultIndices] = useKibanaUiSetting(DEFAULT_INDEX_KEY); - const setupEmbeddable = async () => { - // Configure Embeddables API - try { - setupEmbeddablesAPI(applyFilterQueryFromKueryExpression); - } catch (e) { - displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster); - setIsLoading(false); - setIsError(true); - return false; - } - - // Ensure at least one `siem:defaultIndex` index pattern exists before trying to import - const matchingIndexPatterns = kibanaIndexPatterns.filter(ip => - siemDefaultIndices.includes(ip.attributes.title) - ); - if (matchingIndexPatterns.length === 0) { - setIsLoading(false); - setIsIndexError(true); - return; - } - - // Create & set Embeddable - try { - const embeddableObject = await createEmbeddable( - getIndexPatternTitleIdMapping(matchingIndexPatterns), - queryExpression, - startDate, - endDate, - setQuery + // Initial Load useEffect + useEffect(() => { + let isSubscribed = true; + async function setupEmbeddable() { + // Configure Embeddables API + try { + setupEmbeddablesAPI(applyFilterQueryFromKueryExpression); + } catch (e) { + displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster); + setIsLoading(false); + setIsError(true); + return false; + } + + // Ensure at least one `siem:defaultIndex` index pattern exists before trying to import + const matchingIndexPatterns = kibanaIndexPatterns.filter(ip => + siemDefaultIndices.includes(ip.attributes.title) ); - setEmbeddable(embeddableObject); - } catch (e) { - displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, e.message, dispatchToaster); - setIsError(true); + if (matchingIndexPatterns.length === 0 && isSubscribed) { + setIsLoading(false); + setIsIndexError(true); + return; + } + + // Create & set Embeddable + try { + const embeddableObject = await createEmbeddable( + getIndexPatternTitleIdMapping(matchingIndexPatterns), + queryExpression, + startDate, + endDate, + setQuery + ); + if (isSubscribed) { + setEmbeddable(embeddableObject); + } + } catch (e) { + if (isSubscribed) { + displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, e.message, dispatchToaster); + setIsError(true); + } + } + if (isSubscribed) { + setIsLoading(false); + } } - setIsLoading(false); - }; - // Initial Load useEffect - useEffect(() => { if (!loadingKibanaIndexPatterns) { setupEmbeddable(); } + return () => { + isSubscribed = false; + }; }, [loadingKibanaIndexPatterns, kibanaIndexPatterns]); // queryExpression updated useEffect diff --git a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx index 25536ab19a8cb..bcf5e3c1de408 100644 --- a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx @@ -4,95 +4,83 @@ * you may not use this file except in compliance with the Elastic License. */ -import { cloneDeep } from 'lodash/fp'; import * as React from 'react'; -import { MockedProvider } from 'react-apollo/test-utils'; -import { render } from 'react-testing-library'; import { getEmptyValue } from '../empty_value'; import { LastEventIndexKey } from '../../graphql/types'; import { mockLastEventTimeQuery } from '../../containers/events/last_event_time/mock'; -import { wait } from '../../lib/helpers'; + +import { useLastEventTimeQuery } from '../../containers/events/last_event_time'; import { TestProviders } from '../../mock'; import '../../mock/ui_settings'; import { LastEventTime } from '.'; +import { mount } from 'enzyme'; -describe('Last Event Time Stat', () => { - // this is just a little hack to silence a warning that we'll get until react - // fixes this: https://github.com/facebook/react/pull/14853 - // For us that mean we need to upgrade to 16.9.0 - // and we will be able to do that when we are in master - // eslint-disable-next-line no-console - const originalError = console.error; - - beforeAll(() => { - // eslint-disable-next-line no-console - console.error = (...args: string[]) => { - if (/Warning.*not wrapped in act/.test(args[0])) { - return; - } - originalError.call(console, ...args); - }; - }); +const mockUseLastEventTimeQuery: jest.Mock = useLastEventTimeQuery as jest.Mock; +jest.mock('../../containers/events/last_event_time', () => ({ + useLastEventTimeQuery: jest.fn(), +})); - afterAll(() => { - // eslint-disable-next-line no-console - console.error = originalError; +describe('Last Event Time Stat', () => { + beforeEach(() => { + mockUseLastEventTimeQuery.mockReset(); }); test('Loading', async () => { - const { container } = render( + mockUseLastEventTimeQuery.mockImplementation(() => ({ + loading: true, + lastSeen: null, + errorMessage: null, + })); + const wrapper = mount( - - - + ); - expect(container.innerHTML).toBe( + expect(wrapper.html()).toBe( '' ); }); test('Last seen', async () => { - const { container } = render( + mockUseLastEventTimeQuery.mockImplementation(() => ({ + loading: false, + lastSeen: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.lastSeen, + errorMessage: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.errorMessage, + })); + const wrapper = mount( - - - + ); - await wait(); - expect(container.innerHTML).toBe( - 'Last event: 12 days ago' - ); + expect(wrapper.html()).toBe('Last event: 12 days ago'); }); test('Bad date time string', async () => { - const badDateTime = cloneDeep(mockLastEventTimeQuery); - badDateTime[0].result.data!.source.LastEventTime.lastSeen = 'something-invalid'; - const { container } = render( + mockUseLastEventTimeQuery.mockImplementation(() => ({ + loading: false, + lastSeen: 'something-invalid', + errorMessage: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.errorMessage, + })); + const wrapper = mount( - - - + ); - await wait(); - expect(container.innerHTML).toBe('something-invalid'); + expect(wrapper.html()).toBe('something-invalid'); }); test('Null time string', async () => { - const nullDateTime = cloneDeep(mockLastEventTimeQuery); - nullDateTime[0].result.data!.source.LastEventTime.lastSeen = null; - const { container } = render( + mockUseLastEventTimeQuery.mockImplementation(() => ({ + loading: false, + lastSeen: null, + errorMessage: mockLastEventTimeQuery[0].result.data!.source.LastEventTime.errorMessage, + })); + const wrapper = mount( - - - + ); - await wait(); - - expect(container.innerHTML).toContain(getEmptyValue()); + expect(wrapper.html()).toContain(getEmptyValue()); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.tsx b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.tsx index 9343de8d7ed6b..cb9895bebcf09 100644 --- a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.tsx @@ -6,63 +6,56 @@ import { EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; -import React from 'react'; -import { ApolloConsumer } from 'react-apollo'; -import { pure } from 'recompose'; +import React, { memo } from 'react'; import { LastEventIndexKey } from '../../graphql/types'; import { useLastEventTimeQuery } from '../../containers/events/last_event_time'; import { getEmptyTagValue } from '../empty_value'; -interface LastEventTimeProps { + +export interface LastEventTimeProps { hostName?: string; indexKey: LastEventIndexKey; ip?: string; } -export const LastEventTime = pure(({ hostName, indexKey, ip }) => { +export const LastEventTime = memo(({ hostName, indexKey, ip }) => { + const { loading, lastSeen, errorMessage } = useLastEventTimeQuery( + indexKey, + { hostName, ip }, + 'default' + ); + + if (errorMessage != null) { + return ( + + + + ); + } return ( - - {client => { - const { loading, lastSeen, errorMessage } = useLastEventTimeQuery( - indexKey, - { hostName, ip }, - 'default', - client - ); - if (errorMessage != null) { - return ( - - + <> + {loading && } + {!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date' + ? lastSeen + : !loading && + lastSeen != null && ( + + , + }} + /> - ); - } - return ( - <> - {loading && } - {!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date' - ? lastSeen - : !loading && - lastSeen != null && ( - - , - }} - /> - - )} - {!loading && lastSeen == null && getEmptyTagValue()} - - ); - }} - + )} + {!loading && lastSeen == null && getEmptyTagValue()} + ); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts index aef2eac8eca79..2178bdb9684f3 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts @@ -71,50 +71,62 @@ export const useAnomaliesTableData = ({ const [anomalyScore] = useKibanaUiSetting(DEFAULT_ANOMALY_SCORE); const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); - const fetchFunc = async ( - influencersInput: InfluencerInput[], - criteriaFieldsInput: CriteriaFields[], - earliestMs: number, - latestMs: number - ) => { - if (userPermissions && !skip && siemJobs.length > 0) { - try { - const data = await anomaliesTableData( - { - jobIds: siemJobs, - criteriaFields: criteriaFieldsInput, - aggregationInterval: 'auto', - threshold: getThreshold(anomalyScore, threshold), - earliestMs, - latestMs, - influencers: influencersInput, - dateFormatTz: timezone, - maxRecords: 500, - maxExamples: 10, - }, - { - 'kbn-version': kbnVersion, + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + setLoading(true); + + async function fetchAnomaliesTableData( + influencersInput: InfluencerInput[], + criteriaFieldsInput: CriteriaFields[], + earliestMs: number, + latestMs: number + ) { + if (userPermissions && !skip && siemJobs.length > 0) { + try { + const data = await anomaliesTableData( + { + jobIds: siemJobs, + criteriaFields: criteriaFieldsInput, + aggregationInterval: 'auto', + threshold: getThreshold(anomalyScore, threshold), + earliestMs, + latestMs, + influencers: influencersInput, + dateFormatTz: timezone, + maxRecords: 500, + maxExamples: 10, + }, + { + 'kbn-version': kbnVersion, + }, + abortCtrl.signal + ); + if (isSubscribed) { + setTableData(data); + setLoading(false); } - ); - setTableData(data); + } catch (error) { + if (isSubscribed) { + errorToToaster({ title: i18n.SIEM_TABLE_FETCH_FAILURE, error, dispatchToaster }); + setLoading(false); + } + } + } else if (!userPermissions && isSubscribed) { setLoading(false); - } catch (error) { - errorToToaster({ title: i18n.SIEM_TABLE_FETCH_FAILURE, error, dispatchToaster }); + } else if (siemJobs.length === 0 && isSubscribed) { setLoading(false); + } else if (isSubscribed) { + setTableData(null); + setLoading(true); } - } else if (!userPermissions) { - setLoading(false); - } else if (siemJobs.length === 0) { - setLoading(false); - } else { - setTableData(null); - setLoading(true); } - }; - useEffect(() => { - setLoading(true); - fetchFunc(influencers, criteriaFields, startDate, endDate); + fetchAnomaliesTableData(influencers, criteriaFields, startDate, endDate); + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; }, [ influencersOrCriteriaToString(influencers), influencersOrCriteriaToString(criteriaFields), diff --git a/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts b/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts index 20529f5ea06d0..7308f8759cf5a 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts @@ -25,7 +25,8 @@ export interface Body { export const anomaliesTableData = async ( body: Body, - headers: Record + headers: Record, + signal: AbortSignal ): Promise => { const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); const response = await fetch(`${chrome.getBasePath()}/api/ml/results/anomalies_table_data`, { @@ -38,6 +39,7 @@ export const anomaliesTableData = async ( 'kbn-xsrf': kbnVersion, ...headers, }, + signal, }); await throwIfNotOk(response); return await response.json(); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts b/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts index 8fbd0b5f0f98f..00120b4383b25 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts @@ -25,7 +25,8 @@ export interface Body { } export const getMlCapabilities = async ( - headers: Record + headers: Record, + signal: AbortSignal ): Promise => { const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); const response = await fetch(`${chrome.getBasePath()}/api/ml/ml_capabilities`, { @@ -37,6 +38,7 @@ export const getMlCapabilities = async ( 'kbn-xsrf': kbnVersion, ...headers, }, + signal, }); await throwIfNotOk(response); return await response.json(); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx index b8cf4b6d4354d..b2470bc0f5abd 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx @@ -25,21 +25,35 @@ export const MlCapabilitiesProvider = React.memo<{ children: JSX.Element }>(({ c const [, dispatchToaster] = useStateToaster(); const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); - const fetchFunc = async () => { - try { - const mlCapabilities = await getMlCapabilities({ 'kbn-version': kbnVersion }); - setCapabilities(mlCapabilities); - } catch (error) { - errorToToaster({ - title: i18n.MACHINE_LEARNING_PERMISSIONS_FAILURE, - error, - dispatchToaster, - }); + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + async function fetchMlCapabilities() { + try { + const mlCapabilities = await getMlCapabilities( + { 'kbn-version': kbnVersion }, + abortCtrl.signal + ); + if (isSubscribed) { + setCapabilities(mlCapabilities); + } + } catch (error) { + if (isSubscribed) { + errorToToaster({ + title: i18n.MACHINE_LEARNING_PERMISSIONS_FAILURE, + error, + dispatchToaster, + }); + } + } } - }; - useEffect(() => { - fetchFunc(); + fetchMlCapabilities(); + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; }, []); return ( diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx index 6f77bf45736ab..ac33de385b585 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx @@ -31,7 +31,10 @@ const emptyIndexPattern: IndexPatternSavedObject[] = []; * * @param headers */ -export const groupsData = async (headers: Record): Promise => { +export const groupsData = async ( + headers: Record, + signal: AbortSignal +): Promise => { const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/groups`, { method: 'GET', @@ -42,6 +45,7 @@ export const groupsData = async (headers: Record): P 'kbn-xsrf': kbnVersion, ...headers, }, + signal, }); await throwIfNotOk(response); return await response.json(); @@ -179,7 +183,8 @@ export const stopDatafeeds = async ( */ export const jobsSummary = async ( jobIds: string[], - headers: Record + headers: Record, + signal: AbortSignal ): Promise => { const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/jobs_summary`, { @@ -192,6 +197,7 @@ export const jobsSummary = async ( 'kbn-system-api': 'true', ...headers, }, + signal, }); await throwIfNotOk(response); return await response.json(); @@ -203,7 +209,8 @@ export const jobsSummary = async ( * @param headers */ export const getIndexPatterns = async ( - headers: Record + headers: Record, + signal: AbortSignal ): Promise => { const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); const response = await fetch( @@ -217,6 +224,7 @@ export const getIndexPatterns = async ( 'kbn-system-api': 'true', ...headers, }, + signal, } ); await throwIfNotOk(response); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_index_patterns.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_index_patterns.tsx index 941b64b4dcc08..3f5f6f9c4e958 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_index_patterns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_index_patterns.tsx @@ -25,23 +25,37 @@ export const useIndexPatterns = (refreshToggle = false): Return => { const [, dispatchToaster] = useStateToaster(); const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); - const fetchFunc = async () => { - try { - const data = await getIndexPatterns({ - 'kbn-version': kbnVersion, - }); - - setIndexPatterns(data); - setIsLoading(false); - } catch (error) { - errorToToaster({ title: i18n.INDEX_PATTERN_FETCH_FAILURE, error, dispatchToaster }); - setIsLoading(false); - } - }; - useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); setIsLoading(true); - fetchFunc(); + + async function fetchIndexPatterns() { + try { + const data = await getIndexPatterns( + { + 'kbn-version': kbnVersion, + }, + abortCtrl.signal + ); + + if (isSubscribed) { + setIndexPatterns(data); + setIsLoading(false); + } + } catch (error) { + if (isSubscribed) { + errorToToaster({ title: i18n.INDEX_PATTERN_FETCH_FAILURE, error, dispatchToaster }); + setIsLoading(false); + } + } + } + + fetchIndexPatterns(); + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; }, [refreshToggle]); return [isLoading, indexPatterns]; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_job_summary_data.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_job_summary_data.tsx index 5b163a793aab0..6ae18bc15ab9c 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_job_summary_data.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_job_summary_data.tsx @@ -32,27 +32,43 @@ export const useJobSummaryData = (jobIds: string[] = [], refreshToggle = false): const [, dispatchToaster] = useStateToaster(); const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); - const fetchFunc = async () => { - if (userPermissions) { - try { - const data: Job[] = await jobsSummary(jobIds, { - 'kbn-version': kbnVersion, - }); - - // TODO: API returns all jobs even though we specified jobIds -- jobsSummary call seems to match request in ML App? - const siemJobs = getSiemJobsFromJobsSummary(data); - - setJobSummaryData(siemJobs); - } catch (error) { - errorToToaster({ title: i18n.JOB_SUMMARY_FETCH_FAILURE, error, dispatchToaster }); + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + setLoading(true); + + async function fetchSiemJobsFromJobsSummary() { + if (userPermissions) { + try { + const data: Job[] = await jobsSummary( + jobIds, + { + 'kbn-version': kbnVersion, + }, + abortCtrl.signal + ); + + // TODO: API returns all jobs even though we specified jobIds -- jobsSummary call seems to match request in ML App? + const siemJobs = getSiemJobsFromJobsSummary(data); + if (isSubscribed) { + setJobSummaryData(siemJobs); + } + } catch (error) { + if (isSubscribed) { + errorToToaster({ title: i18n.JOB_SUMMARY_FETCH_FAILURE, error, dispatchToaster }); + } + } + } + if (isSubscribed) { + setLoading(false); } } - setLoading(false); - }; - useEffect(() => { - setLoading(true); - fetchFunc(); + fetchSiemJobsFromJobsSummary(); + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; }, [refreshToggle, userPermissions]); return [loading, jobSummaryData]; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx index 58b2a7ee0784e..f7f45d67d3468 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx @@ -32,26 +32,41 @@ export const useSiemJobs = (refetchData: boolean): Return => { const [, dispatchToaster] = useStateToaster(); const [kbnVersion] = useKibanaUiSetting(DEFAULT_KBN_VERSION); - const fetchFunc = async () => { - if (userPermissions) { - try { - const data = await groupsData({ - 'kbn-version': kbnVersion, - }); - - const siemJobIds = getSiemJobIdsFromGroupsData(data); - - setSiemJobs(siemJobIds); - } catch (error) { - errorToToaster({ title: i18n.SIEM_JOB_FETCH_FAILURE, error, dispatchToaster }); + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + setLoading(true); + + async function fetchSiemJobIdsFromGroupsData() { + if (userPermissions) { + try { + const data = await groupsData( + { + 'kbn-version': kbnVersion, + }, + abortCtrl.signal + ); + + const siemJobIds = getSiemJobIdsFromGroupsData(data); + if (isSubscribed) { + setSiemJobs(siemJobIds); + } + } catch (error) { + if (isSubscribed) { + errorToToaster({ title: i18n.SIEM_JOB_FETCH_FAILURE, error, dispatchToaster }); + } + } + } + if (isSubscribed) { + setLoading(false); } } - setLoading(false); - }; - useEffect(() => { - setLoading(true); - fetchFunc(); + fetchSiemJobIdsFromGroupsData(); + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; }, [refetchData, userPermissions]); return [loading, siemJobs]; diff --git a/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/index.ts b/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/index.ts index 64f7275b4fc0e..3bfdbae8d30f7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/index.ts +++ b/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import ApolloClient from 'apollo-client'; import { get } from 'lodash/fp'; import React, { useEffect, useState } from 'react'; @@ -15,6 +14,7 @@ import { inputsModel } from '../../../store'; import { QueryTemplateProps } from '../../query_template'; import { LastEventTimeGqlQuery } from './last_event_time.gql_query'; +import { useApolloClient } from '../../../utils/apollo_context'; export interface LastEventTimeArgs { id: string; @@ -32,50 +32,54 @@ export interface OwnProps extends QueryTemplateProps { export function useLastEventTimeQuery( indexKey: LastEventIndexKey, details: LastTimeDetails, - sourceId: string, - apolloClient: ApolloClient + sourceId: string ) { const [loading, updateLoading] = useState(false); const [lastSeen, updateLastSeen] = useState(null); const [errorMessage, updateErrorMessage] = useState(null); const [currentIndexKey, updateCurrentIndexKey] = useState(null); - async function fetchLastEventTime() { + const apolloClient = useApolloClient(); + async function fetchLastEventTime(signal: AbortSignal) { updateLoading(true); - return apolloClient - .query({ - query: LastEventTimeGqlQuery, - fetchPolicy: 'cache-first', - variables: { - sourceId, - indexKey, - details, - defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), - }, - }) - .then( - result => { - updateLoading(false); - updateLastSeen(get('data.source.LastEventTime.lastSeen', result)); - updateErrorMessage(null); - updateCurrentIndexKey(currentIndexKey); - return result; - }, - error => { - updateLoading(false); - updateErrorMessage(error.message); - return error; - } - ); + if (apolloClient) { + apolloClient + .query({ + query: LastEventTimeGqlQuery, + fetchPolicy: 'cache-first', + variables: { + sourceId, + indexKey, + details, + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + }, + context: { + fetchOptions: { + signal, + }, + }, + }) + .then( + result => { + updateLoading(false); + updateLastSeen(get('data.source.LastEventTime.lastSeen', result)); + updateErrorMessage(null); + updateCurrentIndexKey(currentIndexKey); + }, + error => { + updateLoading(false); + updateLastSeen(null); + updateErrorMessage(error.message); + } + ); + } } useEffect(() => { - try { - fetchLastEventTime(); - } catch (err) { - updateLastSeen(null); - updateErrorMessage(err.toString()); - } - }, [indexKey, details.hostName, details.ip]); + const abortCtrl = new AbortController(); + const signal = abortCtrl.signal; + fetchLastEventTime(signal); + return () => abortCtrl.abort(); + }, [apolloClient, indexKey, details.hostName, details.ip]); return { lastSeen, loading, errorMessage }; } diff --git a/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts b/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts index 43f63adcf170b..7d0451adcd18f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts +++ b/x-pack/legacy/plugins/siem/public/containers/hosts/first_last_seen/index.ts @@ -40,7 +40,7 @@ export function useFirstLastSeenHostQuery( const [lastSeen, updateLastSeen] = useState(null); const [errorMessage, updateErrorMessage] = useState(null); - async function fetchFirstLastSeenHost() { + async function fetchFirstLastSeenHost(signal: AbortSignal) { updateLoading(true); return apolloClient .query({ @@ -51,6 +51,11 @@ export function useFirstLastSeenHostQuery( hostName, defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), }, + context: { + fetchOptions: { + signal, + }, + }, }) .then( result => { @@ -58,24 +63,21 @@ export function useFirstLastSeenHostQuery( updateFirstSeen(get('data.source.HostFirstLastSeen.firstSeen', result)); updateLastSeen(get('data.source.HostFirstLastSeen.lastSeen', result)); updateErrorMessage(null); - return result; }, error => { updateLoading(false); + updateFirstSeen(null); + updateLastSeen(null); updateErrorMessage(error.message); - return error; } ); } useEffect(() => { - try { - fetchFirstLastSeenHost(); - } catch (err) { - updateFirstSeen(null); - updateLastSeen(null); - updateErrorMessage(err.toString()); - } + const abortCtrl = new AbortController(); + const signal = abortCtrl.signal; + fetchFirstLastSeenHost(signal); + return () => abortCtrl.abort(); }, []); return { firstSeen, lastSeen, loading, errorMessage };