[ML] APM Latency Correlations: Fix empty state (#109813)
- Correctly renders the empty chart state when no data is available. - Hides the "Click drag to select" and trace samples message when the chart shows an empty state to avoid redundant info. - Adds jest unit tests that would fail with the previously visible loading indicators. - Fix a bug with cancelling search strategies.
This commit is contained in:
parent
d43e9f586b
commit
54a45bba65
|
@ -52,3 +52,12 @@ export interface AsyncSearchProviderProgress {
|
|||
loadedFieldValuePairs: number;
|
||||
loadedHistograms: number;
|
||||
}
|
||||
|
||||
export interface SearchServiceRawResponse {
|
||||
ccsWarning: boolean;
|
||||
log: string[];
|
||||
overallHistogram?: HistogramItem[];
|
||||
percentileThresholdValue?: number;
|
||||
took: number;
|
||||
values: SearchServiceValue[];
|
||||
}
|
||||
|
|
|
@ -33,8 +33,7 @@ export function CorrelationsEmptyStatePrompt() {
|
|||
id="xpack.apm.correlations.noCorrelationsTextLine1"
|
||||
defaultMessage="Correlations will only be identified if they have significant impact."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<br />
|
||||
<FormattedMessage
|
||||
id="xpack.apm.correlations.noCorrelationsTextLine2"
|
||||
defaultMessage="Try selecting another time range or remove any added filter."
|
||||
|
|
|
@ -41,7 +41,6 @@ import { CorrelationsLog } from './correlations_log';
|
|||
import { CorrelationsEmptyStatePrompt } from './empty_state_prompt';
|
||||
import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning';
|
||||
import { CorrelationsProgressControls } from './progress_controls';
|
||||
import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
|
||||
import type { FailedTransactionsCorrelationValue } from '../../../../common/search_strategies/failure_correlations/types';
|
||||
import { Summary } from '../../shared/Summary';
|
||||
import { asPercent } from '../../../../common/utils/formatters';
|
||||
|
@ -70,16 +69,6 @@ export function FailedTransactionsCorrelations({
|
|||
|
||||
const inspectEnabled = uiSettings.get<boolean>(enableInspectEsQueries);
|
||||
|
||||
const searchServicePrams: SearchServiceParams = {
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionType,
|
||||
start,
|
||||
end,
|
||||
};
|
||||
|
||||
const result = useFailedTransactionsCorrelationsFetcher();
|
||||
|
||||
const {
|
||||
|
@ -93,26 +82,30 @@ export function FailedTransactionsCorrelations({
|
|||
} = result;
|
||||
|
||||
const startFetchHandler = useCallback(() => {
|
||||
startFetch(searchServicePrams);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [environment, serviceName, kuery, start, end]);
|
||||
startFetch({
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionType,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
}, [
|
||||
startFetch,
|
||||
environment,
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionType,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
]);
|
||||
|
||||
// start fetching on load
|
||||
// we want this effect to execute exactly once after the component mounts
|
||||
useEffect(() => {
|
||||
if (isRunning) {
|
||||
cancelFetch();
|
||||
}
|
||||
|
||||
startFetchHandler();
|
||||
|
||||
return () => {
|
||||
// cancel any running async partial request when unmounting the component
|
||||
// we want this effect to execute exactly once after the component mounts
|
||||
cancelFetch();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [startFetchHandler]);
|
||||
return cancelFetch;
|
||||
}, [cancelFetch, startFetchHandler]);
|
||||
|
||||
const [
|
||||
selectedSignificantTerm,
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n/react';
|
||||
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { merge } from 'lodash';
|
||||
import { dataPluginMock } from 'src/plugins/data/public/mocks';
|
||||
import type { IKibanaSearchResponse } from 'src/plugins/data/public';
|
||||
import { EuiThemeProvider } from 'src/plugins/kibana_react/common';
|
||||
import { createKibanaReactContext } from 'src/plugins/kibana_react/public';
|
||||
import type { SearchServiceRawResponse } from '../../../../common/search_strategies/correlations/types';
|
||||
import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider';
|
||||
import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context';
|
||||
import {
|
||||
mockApmPluginContextValue,
|
||||
MockApmPluginContextWrapper,
|
||||
} from '../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { fromQuery } from '../../shared/Links/url_helpers';
|
||||
|
||||
import { LatencyCorrelations } from './latency_correlations';
|
||||
|
||||
function Wrapper({
|
||||
children,
|
||||
dataSearchResponse,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
dataSearchResponse: IKibanaSearchResponse<SearchServiceRawResponse>;
|
||||
}) {
|
||||
const mockDataSearch = jest.fn(() => of(dataSearchResponse));
|
||||
|
||||
const dataPluginMockStart = dataPluginMock.createStartContract();
|
||||
const KibanaReactContext = createKibanaReactContext({
|
||||
data: {
|
||||
...dataPluginMockStart,
|
||||
search: {
|
||||
...dataPluginMockStart.search,
|
||||
search: mockDataSearch,
|
||||
},
|
||||
},
|
||||
usageCollection: { reportUiCounter: () => {} },
|
||||
} as Partial<CoreStart>);
|
||||
|
||||
const httpGet = jest.fn();
|
||||
|
||||
const history = createMemoryHistory();
|
||||
jest.spyOn(history, 'push');
|
||||
jest.spyOn(history, 'replace');
|
||||
|
||||
history.replace({
|
||||
pathname: '/services/the-service-name/transactions/view',
|
||||
search: fromQuery({ transactionName: 'the-transaction-name' }),
|
||||
});
|
||||
|
||||
const mockPluginContext = (merge({}, mockApmPluginContextValue, {
|
||||
core: { http: { get: httpGet } },
|
||||
}) as unknown) as ApmPluginContextValue;
|
||||
|
||||
return (
|
||||
<IntlProvider locale="en">
|
||||
<EuiThemeProvider darkMode={false}>
|
||||
<KibanaReactContext.Provider>
|
||||
<MockApmPluginContextWrapper
|
||||
history={history}
|
||||
value={mockPluginContext}
|
||||
>
|
||||
<MockUrlParamsContextProvider
|
||||
params={{
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
start: 'mystart',
|
||||
end: 'myend',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MockUrlParamsContextProvider>
|
||||
</MockApmPluginContextWrapper>
|
||||
</KibanaReactContext.Provider>
|
||||
</EuiThemeProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('correlations', () => {
|
||||
describe('LatencyCorrelations', () => {
|
||||
it('shows loading indicator when the service is running and returned no results yet', async () => {
|
||||
render(
|
||||
<Wrapper
|
||||
dataSearchResponse={{
|
||||
isRunning: true,
|
||||
rawResponse: { ccsWarning: false, took: 1234, values: [], log: [] },
|
||||
}}
|
||||
>
|
||||
<LatencyCorrelations onFilter={jest.fn()} />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't show loading indicator when the service isn't running", async () => {
|
||||
render(
|
||||
<Wrapper
|
||||
dataSearchResponse={{
|
||||
isRunning: false,
|
||||
rawResponse: { ccsWarning: false, took: 1234, values: [], log: [] },
|
||||
}}
|
||||
>
|
||||
<LatencyCorrelations onFilter={jest.fn()} />
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('loading')).toBeNull(); // it doesn't exist
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -61,7 +61,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
|
|||
|
||||
const {
|
||||
query: { kuery, environment, rangeFrom, rangeTo },
|
||||
} = useApmParams('/services/:serviceName');
|
||||
} = useApmParams('/services/:serviceName/transactions/view');
|
||||
|
||||
const { urlParams } = useUrlParams();
|
||||
|
||||
|
@ -95,25 +95,21 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
|
|||
end,
|
||||
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [environment, serviceName, kuery, start, end]);
|
||||
}, [
|
||||
startFetch,
|
||||
environment,
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionType,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
]);
|
||||
|
||||
// start fetching on load
|
||||
// we want this effect to execute exactly once after the component mounts
|
||||
useEffect(() => {
|
||||
if (isRunning) {
|
||||
cancelFetch();
|
||||
}
|
||||
|
||||
startFetchHandler();
|
||||
|
||||
return () => {
|
||||
// cancel any running async partial request when unmounting the component
|
||||
// we want this effect to execute exactly once after the component mounts
|
||||
cancelFetch();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [startFetchHandler]);
|
||||
return cancelFetch;
|
||||
}, [cancelFetch, startFetchHandler]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isErrorMessage(error)) {
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getFormattedSelection } from './index';
|
||||
|
||||
describe('transaction_details/distribution', () => {
|
||||
describe('getFormattedSelection', () => {
|
||||
it('displays only one unit if from and to share the same unit', () => {
|
||||
expect(getFormattedSelection([10000, 100000])).toEqual('10 - 100 ms');
|
||||
});
|
||||
|
||||
it('displays two units when from and to have different units', () => {
|
||||
expect(getFormattedSelection([100000, 1000000000])).toEqual(
|
||||
'100 ms - 17 min'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { merge } from 'lodash';
|
||||
import { dataPluginMock } from 'src/plugins/data/public/mocks';
|
||||
import type { IKibanaSearchResponse } from 'src/plugins/data/public';
|
||||
import { EuiThemeProvider } from 'src/plugins/kibana_react/common';
|
||||
import { createKibanaReactContext } from 'src/plugins/kibana_react/public';
|
||||
import type { SearchServiceRawResponse } from '../../../../../common/search_strategies/correlations/types';
|
||||
import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider';
|
||||
import { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context';
|
||||
import {
|
||||
mockApmPluginContextValue,
|
||||
MockApmPluginContextWrapper,
|
||||
} from '../../../../context/apm_plugin/mock_apm_plugin_context';
|
||||
import { fromQuery } from '../../../shared/Links/url_helpers';
|
||||
|
||||
import { getFormattedSelection, TransactionDistribution } from './index';
|
||||
|
||||
function Wrapper({
|
||||
children,
|
||||
dataSearchResponse,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
dataSearchResponse: IKibanaSearchResponse<SearchServiceRawResponse>;
|
||||
}) {
|
||||
const mockDataSearch = jest.fn(() => of(dataSearchResponse));
|
||||
|
||||
const dataPluginMockStart = dataPluginMock.createStartContract();
|
||||
const KibanaReactContext = createKibanaReactContext({
|
||||
data: {
|
||||
...dataPluginMockStart,
|
||||
search: {
|
||||
...dataPluginMockStart.search,
|
||||
search: mockDataSearch,
|
||||
},
|
||||
},
|
||||
usageCollection: { reportUiCounter: () => {} },
|
||||
} as Partial<CoreStart>);
|
||||
|
||||
const httpGet = jest.fn();
|
||||
|
||||
const history = createMemoryHistory();
|
||||
jest.spyOn(history, 'push');
|
||||
jest.spyOn(history, 'replace');
|
||||
|
||||
history.replace({
|
||||
pathname: '/services/the-service-name/transactions/view',
|
||||
search: fromQuery({ transactionName: 'the-transaction-name' }),
|
||||
});
|
||||
|
||||
const mockPluginContext = (merge({}, mockApmPluginContextValue, {
|
||||
core: { http: { get: httpGet } },
|
||||
}) as unknown) as ApmPluginContextValue;
|
||||
|
||||
return (
|
||||
<EuiThemeProvider darkMode={false}>
|
||||
<KibanaReactContext.Provider>
|
||||
<MockApmPluginContextWrapper
|
||||
history={history}
|
||||
value={mockPluginContext}
|
||||
>
|
||||
<MockUrlParamsContextProvider
|
||||
params={{
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
start: 'mystart',
|
||||
end: 'myend',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MockUrlParamsContextProvider>
|
||||
</MockApmPluginContextWrapper>
|
||||
</KibanaReactContext.Provider>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('transaction_details/distribution', () => {
|
||||
describe('getFormattedSelection', () => {
|
||||
it('displays only one unit if from and to share the same unit', () => {
|
||||
expect(getFormattedSelection([10000, 100000])).toEqual('10 - 100 ms');
|
||||
});
|
||||
|
||||
it('displays two units when from and to have different units', () => {
|
||||
expect(getFormattedSelection([100000, 1000000000])).toEqual(
|
||||
'100 ms - 17 min'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TransactionDistribution', () => {
|
||||
it('shows loading indicator when the service is running and returned no results yet', async () => {
|
||||
const onHasData = jest.fn();
|
||||
render(
|
||||
<Wrapper
|
||||
dataSearchResponse={{
|
||||
isRunning: true,
|
||||
rawResponse: { ccsWarning: false, took: 1234, values: [], log: [] },
|
||||
}}
|
||||
>
|
||||
<TransactionDistribution
|
||||
onChartSelection={jest.fn()}
|
||||
onClearSelection={jest.fn()}
|
||||
onHasData={onHasData}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument();
|
||||
expect(onHasData).toHaveBeenLastCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't show loading indicator when the service isn't running", async () => {
|
||||
const onHasData = jest.fn();
|
||||
render(
|
||||
<Wrapper
|
||||
dataSearchResponse={{
|
||||
isRunning: false,
|
||||
rawResponse: { ccsWarning: false, took: 1234, values: [], log: [] },
|
||||
}}
|
||||
>
|
||||
<TransactionDistribution
|
||||
onChartSelection={jest.fn()}
|
||||
onClearSelection={jest.fn()}
|
||||
onHasData={onHasData}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('loading')).toBeNull(); // it doesn't exist
|
||||
expect(onHasData).toHaveBeenLastCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { BrushEndListener, XYBrushArea } from '@elastic/charts';
|
||||
import {
|
||||
EuiBadge,
|
||||
|
@ -21,7 +21,10 @@ import { getDurationFormatter } from '../../../../../common/utils/formatters';
|
|||
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useTransactionDistributionFetcher } from '../../../../hooks/use_transaction_distribution_fetcher';
|
||||
import { TransactionDistributionChart } from '../../../shared/charts/transaction_distribution_chart';
|
||||
import {
|
||||
OnHasData,
|
||||
TransactionDistributionChart,
|
||||
} from '../../../shared/charts/transaction_distribution_chart';
|
||||
import { useUiTracker } from '../../../../../../observability/public';
|
||||
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
|
||||
import { useApmParams } from '../../../../hooks/use_apm_params';
|
||||
|
@ -47,10 +50,11 @@ export function getFormattedSelection(selection: Selection): string {
|
|||
}`;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
interface TransactionDistributionProps {
|
||||
markerCurrentTransaction?: number;
|
||||
onChartSelection: BrushEndListener;
|
||||
onClearSelection: () => void;
|
||||
onHasData: OnHasData;
|
||||
selection?: Selection;
|
||||
}
|
||||
|
||||
|
@ -58,8 +62,9 @@ export function TransactionDistribution({
|
|||
markerCurrentTransaction,
|
||||
onChartSelection,
|
||||
onClearSelection,
|
||||
onHasData,
|
||||
selection,
|
||||
}: Props) {
|
||||
}: TransactionDistributionProps) {
|
||||
const {
|
||||
core: { notifications },
|
||||
} = useApmPluginContext();
|
||||
|
@ -68,7 +73,7 @@ export function TransactionDistribution({
|
|||
|
||||
const {
|
||||
query: { kuery, environment, rangeFrom, rangeTo },
|
||||
} = useApmParams('/services/:serviceName');
|
||||
} = useApmParams('/services/:serviceName/transactions/view');
|
||||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
|
@ -76,6 +81,16 @@ export function TransactionDistribution({
|
|||
|
||||
const { transactionName } = urlParams;
|
||||
|
||||
const [showSelection, setShowSelection] = useState(false);
|
||||
|
||||
const onTransactionDistributionHasData: OnHasData = useCallback(
|
||||
(hasData) => {
|
||||
setShowSelection(hasData);
|
||||
onHasData(hasData);
|
||||
},
|
||||
[onHasData]
|
||||
);
|
||||
|
||||
const emptySelectionText = i18n.translate(
|
||||
'xpack.apm.transactionDetails.emptySelectionText',
|
||||
{
|
||||
|
@ -93,17 +108,12 @@ export function TransactionDistribution({
|
|||
const {
|
||||
error,
|
||||
percentileThresholdValue,
|
||||
isRunning,
|
||||
startFetch,
|
||||
cancelFetch,
|
||||
transactionDistribution,
|
||||
} = useTransactionDistributionFetcher();
|
||||
|
||||
useEffect(() => {
|
||||
if (isRunning) {
|
||||
cancelFetch();
|
||||
}
|
||||
|
||||
const startFetchHandler = useCallback(() => {
|
||||
startFetch({
|
||||
environment,
|
||||
kuery,
|
||||
|
@ -114,14 +124,21 @@ export function TransactionDistribution({
|
|||
end,
|
||||
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
|
||||
});
|
||||
}, [
|
||||
startFetch,
|
||||
environment,
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionType,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
]);
|
||||
|
||||
return () => {
|
||||
// cancel any running async partial request when unmounting the component
|
||||
// we want this effect to execute exactly once after the component mounts
|
||||
cancelFetch();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [environment, serviceName, kuery, start, end]);
|
||||
useEffect(() => {
|
||||
startFetchHandler();
|
||||
return cancelFetch;
|
||||
}, [cancelFetch, startFetchHandler]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isErrorMessage(error)) {
|
||||
|
@ -166,7 +183,7 @@ export function TransactionDistribution({
|
|||
</h5>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{!selection && (
|
||||
{showSelection && !selection && (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="xs">
|
||||
<EuiFlexItem
|
||||
|
@ -184,7 +201,7 @@ export function TransactionDistribution({
|
|||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{selection && (
|
||||
{showSelection && selection && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge
|
||||
iconType="cross"
|
||||
|
@ -217,6 +234,7 @@ export function TransactionDistribution({
|
|||
markerValue={percentileThresholdValue ?? 0}
|
||||
overallHistogram={transactionDistribution}
|
||||
onChartSelection={onTrackedChartSelection}
|
||||
onHasData={onTransactionDistributionHasData}
|
||||
selection={selection}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
|
||||
|
@ -34,11 +34,17 @@ function TraceSamplesTab({
|
|||
status: waterfallStatus,
|
||||
} = useWaterfallFetcher();
|
||||
|
||||
const [
|
||||
transactionDistributionHasData,
|
||||
setTransactionDistributionHasData,
|
||||
] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TransactionDistribution
|
||||
onChartSelection={selectSampleFromChartSelection}
|
||||
onClearSelection={clearChartSelection}
|
||||
onHasData={setTransactionDistributionHasData}
|
||||
selection={
|
||||
sampleRangeFrom !== undefined && sampleRangeTo !== undefined
|
||||
? [sampleRangeFrom, sampleRangeTo]
|
||||
|
@ -49,15 +55,19 @@ function TraceSamplesTab({
|
|||
}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
{transactionDistributionHasData && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<WaterfallWithSummary
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
isLoading={waterfallStatus === FETCH_STATUS.LOADING}
|
||||
exceedsMax={exceedsMax}
|
||||
traceSamples={traceSamples}
|
||||
/>
|
||||
<WaterfallWithSummary
|
||||
urlParams={urlParams}
|
||||
waterfall={waterfall}
|
||||
isLoading={waterfallStatus === FETCH_STATUS.LOADING}
|
||||
exceedsMax={exceedsMax}
|
||||
traceSamples={traceSamples}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import React from 'react';
|
||||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
|
||||
interface Props {
|
||||
export interface ChartContainerProps {
|
||||
hasData: boolean;
|
||||
status: FETCH_STATUS;
|
||||
height: number;
|
||||
|
@ -24,7 +24,7 @@ export function ChartContainer({
|
|||
status,
|
||||
hasData,
|
||||
id,
|
||||
}: Props) {
|
||||
}: ChartContainerProps) {
|
||||
if (!hasData && status === FETCH_STATUS.LOADING) {
|
||||
return <LoadingChartPlaceholder height={height} />;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import {
|
||||
AnnotationDomainType,
|
||||
AreaSeries,
|
||||
|
@ -35,7 +35,14 @@ import { HistogramItem } from '../../../../../common/search_strategies/correlati
|
|||
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
|
||||
import { ChartContainer } from '../chart_container';
|
||||
import { ChartContainer, ChartContainerProps } from '../chart_container';
|
||||
|
||||
export type TransactionDistributionChartLoadingState = Pick<
|
||||
ChartContainerProps,
|
||||
'hasData' | 'status'
|
||||
>;
|
||||
|
||||
export type OnHasData = (hasData: boolean) => void;
|
||||
|
||||
interface TransactionDistributionChartProps {
|
||||
field?: string;
|
||||
|
@ -46,6 +53,7 @@ interface TransactionDistributionChartProps {
|
|||
markerPercentile: number;
|
||||
overallHistogram?: HistogramItem[];
|
||||
onChartSelection?: BrushEndListener;
|
||||
onHasData?: OnHasData;
|
||||
selection?: [number, number];
|
||||
}
|
||||
|
||||
|
@ -103,6 +111,7 @@ export function TransactionDistributionChart({
|
|||
markerPercentile,
|
||||
overallHistogram,
|
||||
onChartSelection,
|
||||
onHasData,
|
||||
selection,
|
||||
}: TransactionDistributionChartProps) {
|
||||
const chartTheme = useChartTheme();
|
||||
|
@ -154,6 +163,24 @@ export function TransactionDistributionChart({
|
|||
]
|
||||
: undefined;
|
||||
|
||||
const chartLoadingState: TransactionDistributionChartLoadingState = useMemo(
|
||||
() => ({
|
||||
hasData:
|
||||
Array.isArray(patchedOverallHistogram) &&
|
||||
patchedOverallHistogram.length > 0,
|
||||
status: Array.isArray(patchedOverallHistogram)
|
||||
? FETCH_STATUS.SUCCESS
|
||||
: FETCH_STATUS.LOADING,
|
||||
}),
|
||||
[patchedOverallHistogram]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (onHasData) {
|
||||
onHasData(chartLoadingState.hasData);
|
||||
}
|
||||
}, [chartLoadingState, onHasData]);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-test-subj="apmCorrelationsChart"
|
||||
|
@ -161,15 +188,8 @@ export function TransactionDistributionChart({
|
|||
>
|
||||
<ChartContainer
|
||||
height={250}
|
||||
hasData={
|
||||
Array.isArray(patchedOverallHistogram) &&
|
||||
patchedOverallHistogram.length > 0
|
||||
}
|
||||
status={
|
||||
Array.isArray(patchedOverallHistogram)
|
||||
? FETCH_STATUS.SUCCESS
|
||||
: FETCH_STATUS.LOADING
|
||||
}
|
||||
hasData={chartLoadingState.hasData}
|
||||
status={chartLoadingState.status}
|
||||
>
|
||||
<Chart>
|
||||
<Settings
|
||||
|
@ -282,7 +302,8 @@ export function TransactionDistributionChart({
|
|||
fieldName !== undefined &&
|
||||
fieldValue !== undefined && (
|
||||
<AreaSeries
|
||||
id={`apmTransactionDistributionChartAreaSeries${fieldName}${fieldValue}`}
|
||||
// id is used as the label for the legend
|
||||
id={`${fieldName}:${fieldValue}`}
|
||||
xScaleType={ScaleType.Log}
|
||||
yScaleType={ScaleType.Log}
|
||||
data={histogram}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useRef, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import {
|
||||
IKibanaSearchRequest,
|
||||
|
@ -72,62 +72,65 @@ export const useFailedTransactionsCorrelationsFetcher = () => {
|
|||
}));
|
||||
}
|
||||
|
||||
const startFetch = (params: SearchServiceParams) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: undefined,
|
||||
isComplete: false,
|
||||
}));
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
abortCtrl.current = new AbortController();
|
||||
const startFetch = useCallback(
|
||||
(params: SearchServiceParams) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: undefined,
|
||||
isComplete: false,
|
||||
}));
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
abortCtrl.current = new AbortController();
|
||||
|
||||
const req = { params };
|
||||
const req = { params };
|
||||
|
||||
// Submit the search request using the `data.search` service.
|
||||
searchSubscription$.current = data.search
|
||||
.search<IKibanaSearchRequest, IKibanaSearchResponse<RawResponse>>(req, {
|
||||
strategy: FAILED_TRANSACTIONS_CORRELATION_SEARCH_STRATEGY,
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (res: IKibanaSearchResponse<RawResponse>) => {
|
||||
setResponse(res);
|
||||
if (isCompleteResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
// Submit the search request using the `data.search` service.
|
||||
searchSubscription$.current = data.search
|
||||
.search<IKibanaSearchRequest, IKibanaSearchResponse<RawResponse>>(req, {
|
||||
strategy: FAILED_TRANSACTIONS_CORRELATION_SEARCH_STRATEGY,
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (res: IKibanaSearchResponse<RawResponse>) => {
|
||||
setResponse(res);
|
||||
if (isCompleteResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
isRunnning: false,
|
||||
isComplete: true,
|
||||
}));
|
||||
} else if (isErrorResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: (res as unknown) as Error,
|
||||
isRunning: false,
|
||||
}));
|
||||
}
|
||||
},
|
||||
error: (error: Error) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
isRunnning: false,
|
||||
isComplete: true,
|
||||
error,
|
||||
isRunning: false,
|
||||
}));
|
||||
} else if (isErrorResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: (res as unknown) as Error,
|
||||
setIsRunning: false,
|
||||
}));
|
||||
}
|
||||
},
|
||||
error: (error: Error) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error,
|
||||
setIsRunning: false,
|
||||
}));
|
||||
},
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
[data.search, setFetchState]
|
||||
);
|
||||
|
||||
const cancelFetch = () => {
|
||||
const cancelFetch = useCallback(() => {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
searchSubscription$.current = undefined;
|
||||
abortCtrl.current.abort();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
setIsRunning: false,
|
||||
isRunning: false,
|
||||
}));
|
||||
};
|
||||
}, [setFetchState]);
|
||||
|
||||
return {
|
||||
...fetchState,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useRef, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import {
|
||||
IKibanaSearchRequest,
|
||||
|
@ -14,31 +14,21 @@ import {
|
|||
isErrorResponse,
|
||||
} from '../../../../../src/plugins/data/public';
|
||||
import type {
|
||||
HistogramItem,
|
||||
SearchServiceParams,
|
||||
SearchServiceValue,
|
||||
SearchServiceRawResponse,
|
||||
} from '../../common/search_strategies/correlations/types';
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { ApmPluginStartDeps } from '../plugin';
|
||||
|
||||
interface RawResponse {
|
||||
percentileThresholdValue?: number;
|
||||
took: number;
|
||||
values: SearchServiceValue[];
|
||||
overallHistogram: HistogramItem[];
|
||||
log: string[];
|
||||
ccsWarning: boolean;
|
||||
}
|
||||
|
||||
interface TransactionDistributionFetcherState {
|
||||
error?: Error;
|
||||
isComplete: boolean;
|
||||
isRunning: boolean;
|
||||
loaded: number;
|
||||
ccsWarning: RawResponse['ccsWarning'];
|
||||
log: RawResponse['log'];
|
||||
transactionDistribution?: RawResponse['overallHistogram'];
|
||||
percentileThresholdValue?: RawResponse['percentileThresholdValue'];
|
||||
ccsWarning: SearchServiceRawResponse['ccsWarning'];
|
||||
log: SearchServiceRawResponse['log'];
|
||||
transactionDistribution?: SearchServiceRawResponse['overallHistogram'];
|
||||
percentileThresholdValue?: SearchServiceRawResponse['percentileThresholdValue'];
|
||||
timeTook?: number;
|
||||
total: number;
|
||||
}
|
||||
|
@ -63,7 +53,9 @@ export function useTransactionDistributionFetcher() {
|
|||
const abortCtrl = useRef(new AbortController());
|
||||
const searchSubscription$ = useRef<Subscription>();
|
||||
|
||||
function setResponse(response: IKibanaSearchResponse<RawResponse>) {
|
||||
function setResponse(
|
||||
response: IKibanaSearchResponse<SearchServiceRawResponse>
|
||||
) {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
isRunning: response.isRunning || false,
|
||||
|
@ -83,71 +75,81 @@ export function useTransactionDistributionFetcher() {
|
|||
response.rawResponse?.percentileThresholdValue,
|
||||
}
|
||||
: {}),
|
||||
// if loading is done but didn't return any data for the overall histogram,
|
||||
// set it to an empty array so the consuming chart component knows loading is done.
|
||||
...(!response.isRunning &&
|
||||
response.rawResponse?.overallHistogram === undefined
|
||||
? { transactionDistribution: [] }
|
||||
: {}),
|
||||
}));
|
||||
}
|
||||
|
||||
const startFetch = (
|
||||
params: Omit<SearchServiceParams, 'analyzeCorrelations'>
|
||||
) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: undefined,
|
||||
isComplete: false,
|
||||
}));
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
abortCtrl.current = new AbortController();
|
||||
const startFetch = useCallback(
|
||||
(params: Omit<SearchServiceParams, 'analyzeCorrelations'>) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: undefined,
|
||||
isComplete: false,
|
||||
}));
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
abortCtrl.current = new AbortController();
|
||||
|
||||
const searchServiceParams: SearchServiceParams = {
|
||||
...params,
|
||||
analyzeCorrelations: false,
|
||||
};
|
||||
const req = { params: searchServiceParams };
|
||||
const searchServiceParams: SearchServiceParams = {
|
||||
...params,
|
||||
analyzeCorrelations: false,
|
||||
};
|
||||
const req = { params: searchServiceParams };
|
||||
|
||||
// Submit the search request using the `data.search` service.
|
||||
searchSubscription$.current = data.search
|
||||
.search<IKibanaSearchRequest, IKibanaSearchResponse<RawResponse>>(req, {
|
||||
strategy: 'apmCorrelationsSearchStrategy',
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (res: IKibanaSearchResponse<RawResponse>) => {
|
||||
setResponse(res);
|
||||
if (isCompleteResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
// Submit the search request using the `data.search` service.
|
||||
searchSubscription$.current = data.search
|
||||
.search<
|
||||
IKibanaSearchRequest,
|
||||
IKibanaSearchResponse<SearchServiceRawResponse>
|
||||
>(req, {
|
||||
strategy: 'apmCorrelationsSearchStrategy',
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (res: IKibanaSearchResponse<SearchServiceRawResponse>) => {
|
||||
setResponse(res);
|
||||
if (isCompleteResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
isRunnning: false,
|
||||
isComplete: true,
|
||||
}));
|
||||
} else if (isErrorResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: (res as unknown) as Error,
|
||||
isRunning: false,
|
||||
}));
|
||||
}
|
||||
},
|
||||
error: (error: Error) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
isRunnning: false,
|
||||
isComplete: true,
|
||||
error,
|
||||
isRunning: false,
|
||||
}));
|
||||
} else if (isErrorResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: (res as unknown) as Error,
|
||||
setIsRunning: false,
|
||||
}));
|
||||
}
|
||||
},
|
||||
error: (error: Error) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error,
|
||||
setIsRunning: false,
|
||||
}));
|
||||
},
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
[data.search, setFetchState]
|
||||
);
|
||||
|
||||
const cancelFetch = () => {
|
||||
const cancelFetch = useCallback(() => {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
searchSubscription$.current = undefined;
|
||||
abortCtrl.current.abort();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
setIsRunning: false,
|
||||
isRunning: false,
|
||||
}));
|
||||
};
|
||||
}, [setFetchState]);
|
||||
|
||||
return {
|
||||
...fetchState,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useRef, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import {
|
||||
IKibanaSearchRequest,
|
||||
|
@ -14,32 +14,22 @@ import {
|
|||
isErrorResponse,
|
||||
} from '../../../../../src/plugins/data/public';
|
||||
import type {
|
||||
HistogramItem,
|
||||
SearchServiceParams,
|
||||
SearchServiceValue,
|
||||
SearchServiceRawResponse,
|
||||
} from '../../common/search_strategies/correlations/types';
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { ApmPluginStartDeps } from '../plugin';
|
||||
|
||||
interface RawResponse {
|
||||
percentileThresholdValue?: number;
|
||||
took: number;
|
||||
values: SearchServiceValue[];
|
||||
overallHistogram: HistogramItem[];
|
||||
log: string[];
|
||||
ccsWarning: boolean;
|
||||
}
|
||||
|
||||
interface TransactionLatencyCorrelationsFetcherState {
|
||||
error?: Error;
|
||||
isComplete: boolean;
|
||||
isRunning: boolean;
|
||||
loaded: number;
|
||||
ccsWarning: RawResponse['ccsWarning'];
|
||||
histograms: RawResponse['values'];
|
||||
log: RawResponse['log'];
|
||||
overallHistogram?: RawResponse['overallHistogram'];
|
||||
percentileThresholdValue?: RawResponse['percentileThresholdValue'];
|
||||
ccsWarning: SearchServiceRawResponse['ccsWarning'];
|
||||
histograms: SearchServiceRawResponse['values'];
|
||||
log: SearchServiceRawResponse['log'];
|
||||
overallHistogram?: SearchServiceRawResponse['overallHistogram'];
|
||||
percentileThresholdValue?: SearchServiceRawResponse['percentileThresholdValue'];
|
||||
timeTook?: number;
|
||||
total: number;
|
||||
}
|
||||
|
@ -65,7 +55,9 @@ export const useTransactionLatencyCorrelationsFetcher = () => {
|
|||
const abortCtrl = useRef(new AbortController());
|
||||
const searchSubscription$ = useRef<Subscription>();
|
||||
|
||||
function setResponse(response: IKibanaSearchResponse<RawResponse>) {
|
||||
function setResponse(
|
||||
response: IKibanaSearchResponse<SearchServiceRawResponse>
|
||||
) {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
isRunning: response.isRunning || false,
|
||||
|
@ -85,71 +77,86 @@ export const useTransactionLatencyCorrelationsFetcher = () => {
|
|||
response.rawResponse?.percentileThresholdValue,
|
||||
}
|
||||
: {}),
|
||||
// if loading is done but didn't return any data for the overall histogram,
|
||||
// set it to an empty array so the consuming chart component knows loading is done.
|
||||
...(!response.isRunning &&
|
||||
response.rawResponse?.overallHistogram === undefined
|
||||
? { overallHistogram: [] }
|
||||
: {}),
|
||||
}));
|
||||
}
|
||||
|
||||
const startFetch = (
|
||||
params: Omit<SearchServiceParams, 'analyzeCorrelations'>
|
||||
) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: undefined,
|
||||
isComplete: false,
|
||||
}));
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
abortCtrl.current = new AbortController();
|
||||
const startFetch = useCallback(
|
||||
(params: Omit<SearchServiceParams, 'analyzeCorrelations'>) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: undefined,
|
||||
isComplete: false,
|
||||
}));
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
abortCtrl.current = new AbortController();
|
||||
|
||||
const searchServiceParams: SearchServiceParams = {
|
||||
...params,
|
||||
analyzeCorrelations: true,
|
||||
};
|
||||
const req = { params: searchServiceParams };
|
||||
const searchServiceParams: SearchServiceParams = {
|
||||
...params,
|
||||
analyzeCorrelations: true,
|
||||
};
|
||||
const req = { params: searchServiceParams };
|
||||
|
||||
// Submit the search request using the `data.search` service.
|
||||
searchSubscription$.current = data.search
|
||||
.search<IKibanaSearchRequest, IKibanaSearchResponse<RawResponse>>(req, {
|
||||
strategy: 'apmCorrelationsSearchStrategy',
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (res: IKibanaSearchResponse<RawResponse>) => {
|
||||
setResponse(res);
|
||||
if (isCompleteResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
// Submit the search request using the `data.search` service.
|
||||
searchSubscription$.current = data.search
|
||||
.search<
|
||||
IKibanaSearchRequest,
|
||||
IKibanaSearchResponse<SearchServiceRawResponse>
|
||||
>(req, {
|
||||
strategy: 'apmCorrelationsSearchStrategy',
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (res: IKibanaSearchResponse<SearchServiceRawResponse>) => {
|
||||
setResponse(res);
|
||||
if (isCompleteResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
isRunnning: false,
|
||||
isComplete: true,
|
||||
}));
|
||||
} else if (isErrorResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: (res as unknown) as Error,
|
||||
isRunning: false,
|
||||
}));
|
||||
}
|
||||
},
|
||||
error: (error: Error) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
isRunnning: false,
|
||||
isComplete: true,
|
||||
error,
|
||||
isRunning: false,
|
||||
}));
|
||||
} else if (isErrorResponse(res)) {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error: (res as unknown) as Error,
|
||||
setIsRunning: false,
|
||||
}));
|
||||
}
|
||||
},
|
||||
error: (error: Error) => {
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
error,
|
||||
setIsRunning: false,
|
||||
}));
|
||||
},
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
[data.search, setFetchState]
|
||||
);
|
||||
|
||||
const cancelFetch = () => {
|
||||
const cancelFetch = useCallback(() => {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
searchSubscription$.current = undefined;
|
||||
abortCtrl.current.abort();
|
||||
setFetchState((prevState) => ({
|
||||
...prevState,
|
||||
setIsRunning: false,
|
||||
// If we didn't receive data for the overall histogram yet
|
||||
// set it to an empty array to indicate loading stopped.
|
||||
...(prevState.overallHistogram === undefined
|
||||
? { overallHistogram: [] }
|
||||
: {}),
|
||||
isRunning: false,
|
||||
}));
|
||||
};
|
||||
}, [setFetchState]);
|
||||
|
||||
return {
|
||||
...fetchState,
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
|
||||
import type {
|
||||
SearchServiceParams,
|
||||
SearchServiceRawResponse,
|
||||
SearchServiceValue,
|
||||
} from '../../../../common/search_strategies/correlations/types';
|
||||
|
||||
|
@ -100,20 +101,22 @@ export const apmCorrelationsSearchStrategyProvider = (
|
|||
|
||||
const took = Date.now() - started;
|
||||
|
||||
const rawResponse: SearchServiceRawResponse = {
|
||||
ccsWarning,
|
||||
log,
|
||||
took,
|
||||
values,
|
||||
percentileThresholdValue,
|
||||
overallHistogram,
|
||||
};
|
||||
|
||||
return of({
|
||||
id,
|
||||
loaded,
|
||||
total,
|
||||
isRunning,
|
||||
isPartial: isRunning,
|
||||
rawResponse: {
|
||||
ccsWarning,
|
||||
log,
|
||||
took,
|
||||
values,
|
||||
percentileThresholdValue,
|
||||
overallHistogram,
|
||||
},
|
||||
rawResponse,
|
||||
});
|
||||
},
|
||||
cancel: async (id, options, deps) => {
|
||||
|
|
Loading…
Reference in a new issue