diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx index a4be2bb8d773d..3623aee43c7d1 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx @@ -19,6 +19,7 @@ async function renderTooltipAnchor({ }) { // mock api response jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { jobs }, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx index 03c826b50a06b..b6311e9135695 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.test.tsx @@ -14,6 +14,7 @@ import { render } from '../../utils/test_helper'; describe('SelectableUrlList', () => { it('it uses search term value from url', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: {}, status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx index e1253b41a45f5..d7edc0402d9be 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.test.tsx @@ -13,6 +13,7 @@ import { KeyUXMetrics } from './KeyUXMetrics'; describe('KeyUXMetrics', () => { it('renders metrics with correct formats', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { noOfLongTasks: 3.0009765625, sumOfLongTasks: 520.4375, diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx index e8384de1d15ba..a18e88238ce39 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx @@ -90,6 +90,7 @@ describe('ServiceMap', () => { describe('with an empty response', () => { it('renders the empty banner', async () => { jest.spyOn(useFetcherModule, 'useFetcher').mockReturnValueOnce({ + requestId: 'foo', data: { elements: [] }, refetch: () => {}, status: useFetcherModule.FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx index 4d2754a677bf7..c7844eaaeb6e4 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx @@ -53,6 +53,7 @@ storiesOf( { it('should not get stuck in infinite loop', () => { const spy = jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: undefined, status: hooks.FETCH_STATUS.LOADING, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx index 0dbc8f6235342..ff15a3069ba8b 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx @@ -69,6 +69,7 @@ describe('CustomLink', () => { describe('empty prompt', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -93,6 +94,7 @@ describe('CustomLink', () => { describe('overview', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -172,6 +174,7 @@ describe('CustomLink', () => { beforeAll(() => { saveCustomLinkSpy = jest.spyOn(saveCustomLink, 'saveCustomLink'); jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data, status: hooks.FETCH_STATUS.SUCCESS, refetch, @@ -290,6 +293,7 @@ describe('CustomLink', () => { describe('invalid license', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx index be23c19e0664d..93e51345d9204 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx @@ -71,6 +71,7 @@ describe('TraceLink', () => { }, }); jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { transaction: undefined }, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -111,6 +112,7 @@ describe('TraceLink', () => { trace: { id: 123 }, }; jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { transaction }, status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx b/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx index dbf4b65deb3b3..0b6d14b0f23dd 100644 --- a/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_details/service_icons/index.test.tsx @@ -54,6 +54,7 @@ describe('ServiceIcons', () => { describe('icons', () => { it('Shows loading spinner while fetching data', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: undefined, status: fetcherHook.FETCH_STATUS.LOADING, refetch: jest.fn(), @@ -70,6 +71,7 @@ describe('ServiceIcons', () => { }); it("doesn't show any icons", () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: {}, status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -87,6 +89,7 @@ describe('ServiceIcons', () => { }); it('shows service icon', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { agentName: 'java', }, @@ -106,6 +109,7 @@ describe('ServiceIcons', () => { }); it('shows service and container icons', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { agentName: 'java', containerType: 'Kubernetes', @@ -126,6 +130,7 @@ describe('ServiceIcons', () => { }); it('shows service, container and cloud icons', () => { jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: { agentName: 'java', containerType: 'Kubernetes', diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index 7575cdcea4aca..379b37ed959b2 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -59,7 +59,7 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { urlParams: { start, end, latencyAggregationType }, } = useUrlParams(); - const { data = INITIAL_STATE, status } = useFetcher( + const { data = INITIAL_STATE, status, requestId } = useFetcher( (callApmApi) => { if (!start || !end || !latencyAggregationType || !transactionType) { return; @@ -130,9 +130,9 @@ export function ServiceOverviewTransactionsTable({ serviceName }: Props) { }); } }, - // only fetches statistics when transaction names changes + // only fetches statistics when transaction names or requestId changes // eslint-disable-next-line react-hooks/exhaustive-deps - [transactionNames], + [requestId, transactionNames], { preservePreviousData: false } ); diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx index 6f2910a2a5ef7..7f2a5edf50ca3 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx @@ -40,6 +40,7 @@ const transaction = ({ describe('Custom links', () => { it('shows empty message when no custom link is available', () => { jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -58,6 +59,7 @@ describe('Custom links', () => { it('shows loading while custom links are fetched', () => { jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: useFetcher.FETCH_STATUS.LOADING, refetch: jest.fn(), @@ -79,6 +81,7 @@ describe('Custom links', () => { ] as CustomLinkType[]; jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: customLinks, status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -101,6 +104,7 @@ describe('Custom links', () => { ] as CustomLinkType[]; jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: customLinks, status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -125,6 +129,7 @@ describe('Custom links', () => { describe('create custom link buttons', () => { it('shows create button below empty message', () => { jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), @@ -148,6 +153,7 @@ describe('Custom links', () => { ] as CustomLinkType[]; jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: customLinks, status: useFetcher.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx index 0b0d5c8b1b8a2..7fc2907525663 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.test.tsx @@ -61,6 +61,7 @@ const renderTransaction = async (transaction: Record) => { describe('TransactionActionMenu component', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + requestId: 'foo', data: [], status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx index 546b039f7ba8a..856fc0c3830f6 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx @@ -33,6 +33,7 @@ describe('useFetcher', () => { it('should have loading spinner initally', async () => { expect(hook.result.current).toEqual({ + requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -44,6 +45,7 @@ describe('useFetcher', () => { jest.advanceTimersByTime(100); expect(hook.result.current).toEqual({ + requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -56,6 +58,7 @@ describe('useFetcher', () => { await hook.waitForNextUpdate(); expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: 'response from hook', error: undefined, refetch: expect.any(Function), @@ -82,6 +85,7 @@ describe('useFetcher', () => { it('should have loading spinner initally', async () => { expect(hook.result.current).toEqual({ + requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -93,6 +97,7 @@ describe('useFetcher', () => { jest.advanceTimersByTime(100); expect(hook.result.current).toEqual({ + requestId: '', data: undefined, error: undefined, refetch: expect.any(Function), @@ -105,6 +110,7 @@ describe('useFetcher', () => { await hook.waitForNextUpdate(); expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: undefined, error: expect.any(Error), refetch: expect.any(Function), @@ -129,6 +135,7 @@ describe('useFetcher', () => { } ); expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: undefined, error: undefined, refetch: expect.any(Function), @@ -139,6 +146,7 @@ describe('useFetcher', () => { // assert: first response has loaded and should be rendered expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: 'first response', error: undefined, refetch: expect.any(Function), @@ -158,6 +166,7 @@ describe('useFetcher', () => { // assert: while loading new data the previous data should still be rendered expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: 'first response', error: undefined, refetch: expect.any(Function), @@ -169,6 +178,7 @@ describe('useFetcher', () => { // assert: "second response" has loaded and should be rendered expect(hook.result.current).toEqual({ + requestId: expect.any(String), data: 'second response', error: undefined, refetch: expect.any(Function), diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx index e427c5719aeb8..1770001d57538 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useMemo, useState } from 'react'; import { IHttpFetchError } from 'src/core/public'; +import uuid from 'uuid'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; import { callApmApi, @@ -24,6 +25,7 @@ export enum FETCH_STATUS { } export interface FetcherResult { + requestId: string; data?: Data; status: FETCH_STATUS; error?: IHttpFetchError; @@ -75,6 +77,7 @@ export function useFetcher( >({ data: undefined, status: FETCH_STATUS.NOT_INITIATED, + requestId: '', }); const [counter, setCounter] = useState(0); const { rangeId } = useUrlParams(); @@ -83,6 +86,7 @@ export function useFetcher( let controller: AbortController = new AbortController(); async function doFetch() { + const requestId = uuid(); controller.abort(); controller = new AbortController(); @@ -98,6 +102,7 @@ export function useFetcher( } setResult((prevResult) => ({ + requestId: prevResult.requestId, data: preservePreviousData ? prevResult.data : undefined, // preserve data from previous state while loading next state status: FETCH_STATUS.LOADING, error: undefined, @@ -111,6 +116,7 @@ export function useFetcher( // aborted before updating the result. if (!signal.aborted) { setResult({ + requestId, data, status: FETCH_STATUS.SUCCESS, error: undefined, @@ -143,6 +149,7 @@ export function useFetcher( }); } setResult({ + requestId, data: undefined, status: FETCH_STATUS.FAILURE, error: e,