[Performance] Track performance telemetry in APM (#208561)

## Summary

Closes https://github.com/elastic/kibana/issues/205396

This PR adds performance telemetry to the missing APM pages
This commit is contained in:
Sergi Romeu 2025-02-04 16:30:47 +01:00 committed by GitHub
parent df573d7596
commit 23d926f096
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 287 additions and 28 deletions

View file

@ -7,10 +7,11 @@
import { METRIC_TYPE } from '@kbn/analytics';
import { i18n } from '@kbn/i18n';
import React from 'react';
import React, { useEffect } from 'react';
import { useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { useUiTracker } from '@kbn/observability-shared-plugin/public';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { getNodeName, NodeType } from '../../../../../common/connections';
import { useApmParams } from '../../../../hooks/use_apm_params';
@ -24,7 +25,7 @@ export function DependenciesInventoryTable() {
const {
query: { rangeFrom, rangeTo, environment, kuery, comparisonEnabled, offset },
} = useApmParams('/dependencies/inventory');
const { onPageReady } = usePerformanceContext();
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const { euiTheme } = useEuiTheme();
@ -52,6 +53,17 @@ export function DependenciesInventoryTable() {
[start, end, environment, offset, kuery, comparisonEnabled]
);
useEffect(() => {
if (status === FETCH_STATUS.SUCCESS) {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
});
}
}, [status, onPageReady, rangeFrom, rangeTo]);
const dependencies =
data?.dependencies.map((dependency) => {
const { location } = dependency;

View file

@ -7,7 +7,8 @@
import { i18n } from '@kbn/i18n';
import { keyBy } from 'lodash';
import React from 'react';
import React, { useEffect } from 'react';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { useSearchServiceDestinationMetrics } from '../../../../context/time_range_metadata/use_search_service_destination_metrics';
import { useApmParams } from '../../../../hooks/use_apm_params';
@ -59,7 +60,7 @@ export function DependencyDetailOperationsList() {
offset,
},
} = useApmParams('/dependencies/operations');
const { onPageReady } = usePerformanceContext();
const { core } = useApmPluginContext();
const { isLarge } = useBreakpoints();
@ -132,6 +133,20 @@ export function DependencyDetailOperationsList() {
]
);
useEffect(() => {
if (
comparisonStatsFetch.status === FETCH_STATUS.SUCCESS &&
primaryStatsFetch.status === FETCH_STATUS.SUCCESS
) {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
});
}
}, [onPageReady, primaryStatsFetch, comparisonStatsFetch, rangeFrom, rangeTo]);
const columns: Array<ITableColumn<OperationStatisticsItem>> = [
{
name: i18n.translate('xpack.apm.dependencyDetailOperationsList.spanNameColumnLabel', {

View file

@ -6,11 +6,12 @@
*/
import { i18n } from '@kbn/i18n';
import React from 'react';
import React, { useEffect } from 'react';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { isTimeComparison } from '../../shared/time_comparison/get_comparison_options';
import { getNodeName, NodeType } from '../../../../common/connections';
import { useApmParams } from '../../../hooks/use_apm_params';
import { useFetcher } from '../../../hooks/use_fetcher';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import { DependenciesTable } from '../../shared/dependencies_table';
import { ServiceLink } from '../../shared/links/apm/service_link';
import { useTimeRange } from '../../../hooks/use_time_range';
@ -31,6 +32,7 @@ export function DependenciesDetailTable() {
} = useApmParams('/dependencies/overview');
const { core } = useApmPluginContext();
const { onPageReady } = usePerformanceContext();
const comparisonEnabled = getComparisonEnabled({
core,
@ -58,6 +60,17 @@ export function DependenciesDetailTable() {
[start, end, environment, offset, dependencyName, kuery, comparisonEnabled]
);
useEffect(() => {
if (status === FETCH_STATUS.SUCCESS) {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
});
}
}, [onPageReady, status, rangeFrom, rangeTo]);
const dependencies =
data?.services.map((dependency) => {
const { location } = dependency;

View file

@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import React, { useEffect } from 'react';
import { omit } from 'lodash';
import { useHistory } from 'react-router-dom';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { isOpenTelemetryAgentName, isRumAgentName } from '../../../../common/agent_name';
import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
@ -82,7 +83,7 @@ export function ErrorGroupDetails() {
const apmRouter = useApmRouter();
const history = useHistory();
const { onPageReady } = usePerformanceContext();
const { observabilityAIAssistant } = useApmPluginContext();
const {
@ -170,6 +171,20 @@ export function ErrorGroupDetails() {
}
}, [history, errorId, errorSamplesData, errorSamplesFetchStatus]);
useEffect(() => {
if (
errorSamplesFetchStatus === FETCH_STATUS.SUCCESS &&
errorDistributionStatus === FETCH_STATUS.SUCCESS
) {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
});
}
}, [onPageReady, errorSamplesFetchStatus, errorDistributionStatus, rangeFrom, rangeTo]);
const { agentName } = useApmServiceContext();
const isOpenTelemetryAgent = isOpenTelemetryAgentName(agentName as AgentName);
const isRumAgent = isRumAgentName(agentName as AgentName);

View file

@ -10,6 +10,13 @@ import { render } from '@testing-library/react';
import React from 'react';
import * as stories from './error_group_list.stories';
// Mock the usePerformanceContext hook
jest.mock('@kbn/ebt-tools', () => ({
usePerformanceContext: () => ({
onPageReady: jest.fn(),
}),
}));
const { Example } = composeStories(stories);
describe('ErrorGroupList', () => {

View file

@ -8,9 +8,10 @@
import { EuiBadge, EuiIconTip, EuiToolTip, RIGHT_ALIGNMENT } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import styled from '@emotion/styled';
import React, { useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState, useRef } from 'react';
import { apmEnableTableSearchBar } from '@kbn/observability-plugin/common';
import { isPending } from '../../../../hooks/use_fetcher';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { FETCH_STATUS, isPending } from '../../../../hooks/use_fetcher';
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
import { asBigNumber } from '../../../../../common/utils/formatters';
import { useAnyOfApmParams } from '../../../../hooks/use_apm_params';
@ -55,6 +56,7 @@ interface Props {
comparisonEnabled?: boolean;
saveTableOptionsToUrl?: boolean;
showPerPageOptions?: boolean;
onLoadTable?: () => void;
}
const defaultSorting = {
@ -69,6 +71,7 @@ export function ErrorGroupList({
comparisonEnabled,
saveTableOptionsToUrl,
showPerPageOptions = true,
onLoadTable,
}: Props) {
const { query } = useAnyOfApmParams(
'/services/{serviceName}/overview',
@ -79,10 +82,10 @@ export function ErrorGroupList({
const isTableSearchBarEnabled = core.uiSettings.get<boolean>(apmEnableTableSearchBar, true);
const { offset } = query;
const { offset, rangeFrom, rangeTo } = query;
const [renderedItems, setRenderedItems] = useState<ErrorGroupItem[]>([]);
const hasTableLoaded = useRef(false);
const [sorting, setSorting] = useState<TableOptions<ErrorGroupItem>['sort']>(defaultSorting);
const {
@ -95,6 +98,36 @@ export function ErrorGroupList({
const isMainStatsLoading = isPending(mainStatisticsStatus);
const isDetailedStatsLoading = isPending(detailedStatisticsStatus);
const { onPageReady } = usePerformanceContext();
useEffect(() => {
// this component is used both for the service overview tab and the errors tab,
// onLoadTable will be defined if it's the service overview tab
if (
mainStatisticsStatus === FETCH_STATUS.SUCCESS &&
detailedStatisticsStatus === FETCH_STATUS.SUCCESS &&
!hasTableLoaded.current
) {
if (onLoadTable) {
onLoadTable();
} else {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
});
}
hasTableLoaded.current = true;
}
}, [
mainStatisticsStatus,
detailedStatisticsStatus,
onLoadTable,
rangeFrom,
rangeTo,
onPageReady,
]);
const columns = useMemo(() => {
const groupIdColumn: ITableColumn<ErrorGroupItem> = {

View file

@ -7,7 +7,8 @@
import type { EuiFlexGroupProps } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiPanel, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { chartHeight } from '..';
import type { AgentName } from '../../../../../typings/es_schemas/ui/fields/agent';
import {
@ -44,6 +45,24 @@ export function ApmOverview() {
} = useApmParams('/services/{serviceName}/overview');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const [haveTablesLoaded, setHaveTablesLoaded] = useState({
transactions: false,
dependencies: false,
errors: false,
});
const { onPageReady } = usePerformanceContext();
useEffect(() => {
const { transactions, dependencies, errors } = haveTablesLoaded;
if (transactions && dependencies && errors) {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
});
}
}, [haveTablesLoaded, onPageReady, rangeFrom, rangeTo]);
const isRumAgent = isRumAgentName(agentName);
const isOpenTelemetryAgent = isOpenTelemetryAgentName(agentName as AgentName);
@ -62,6 +81,10 @@ export function ApmOverview() {
false
);
const onLoadTable = (key: string) => {
setHaveTablesLoaded((currentValues) => ({ ...currentValues, [key]: true }));
};
return (
<>
{!sloCalloutDismissed && (
@ -98,6 +121,7 @@ export function ApmOverview() {
kuery={kuery}
environment={environment}
fixedHeight={true}
onLoadTable={() => onLoadTable('transactions')}
start={start}
end={end}
showPerPageOptions={false}
@ -121,7 +145,10 @@ export function ApmOverview() {
)}
<EuiFlexItem grow={7}>
<EuiPanel hasBorder={true}>
<ServiceOverviewErrorsTable serviceName={serviceName} />
<ServiceOverviewErrorsTable
serviceName={serviceName}
onLoadTable={() => onLoadTable('errors')}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
@ -151,6 +178,7 @@ export function ApmOverview() {
<EuiFlexItem grow={7}>
<EuiPanel hasBorder={true}>
<ServiceOverviewDependenciesTable
onLoadTable={() => onLoadTable('dependencies')}
fixedHeight={true}
showPerPageOptions={false}
link={

View file

@ -12,6 +12,13 @@ import * as stories from './service_overview.stories';
import * as useAdHocApmDataView from '../../../hooks/use_adhoc_apm_data_view';
import { renderWithTheme } from '../../../utils/test_helpers';
// Mock the usePerformanceContext hook
jest.mock('@kbn/ebt-tools', () => ({
usePerformanceContext: () => ({
onPageReady: jest.fn(),
}),
}));
const { Example } = composeStories(stories);
describe('ServiceOverview', () => {

View file

@ -9,8 +9,9 @@ import { EuiIconTip } from '@elastic/eui';
import { METRIC_TYPE } from '@kbn/analytics';
import { i18n } from '@kbn/i18n';
import type { ReactNode } from 'react';
import React from 'react';
import { useUiTracker } from '@kbn/observability-shared-plugin/public';
import React, { useEffect, useRef } from 'react';
import { FETCH_STATUS, useUiTracker } from '@kbn/observability-shared-plugin/public';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options';
import { getNodeName, NodeType } from '../../../../../common/connections';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
@ -26,6 +27,7 @@ interface ServiceOverviewDependenciesTableProps {
link?: ReactNode;
showPerPageOptions?: boolean;
showSparkPlots?: boolean;
onLoadTable?: () => void;
}
export function ServiceOverviewDependenciesTable({
@ -33,6 +35,7 @@ export function ServiceOverviewDependenciesTable({
link,
showPerPageOptions = true,
showSparkPlots,
onLoadTable,
}: ServiceOverviewDependenciesTableProps) {
const {
query: {
@ -50,9 +53,9 @@ export function ServiceOverviewDependenciesTable({
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const { serviceName, transactionType } = useApmServiceContext();
const { onPageReady } = usePerformanceContext();
const trackEvent = useUiTracker();
const hasTableLoaded = useRef(false);
const { data, status } = useFetcher(
(callApmApi) => {
if (!start || !end) {
@ -75,6 +78,24 @@ export function ServiceOverviewDependenciesTable({
[start, end, serviceName, environment, offset, comparisonEnabled]
);
useEffect(() => {
// this component is used both for the service overview tab and the transactions tab,
// onLoadTable will be defined if it's the service overview tab
if (status === FETCH_STATUS.SUCCESS && !hasTableLoaded.current) {
if (onLoadTable) {
onLoadTable();
} else {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
});
}
hasTableLoaded.current = true;
}
}, [status, onLoadTable, onPageReady, rangeFrom, rangeTo]);
const dependencies =
data?.serviceDependencies.map((dependency) => {
const { location } = dependency;

View file

@ -15,9 +15,10 @@ import { ErrorGroupList } from '../../error_group_overview/error_group_list';
interface Props {
serviceName: string;
onLoadTable?: () => void;
}
export function ServiceOverviewErrorsTable({ serviceName }: Props) {
export function ServiceOverviewErrorsTable({ serviceName, onLoadTable }: Props) {
const { query } = useApmParams('/services/{serviceName}/overview');
return (
@ -46,6 +47,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
<OverviewTableContainer fixedHeight={true} isEmptyAndNotInitiated={false}>
<ErrorGroupList
serviceName={serviceName}
onLoadTable={onLoadTable}
initialPageSize={5}
isCompactMode={true}
saveTableOptionsToUrl={false}

View file

@ -4,7 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useMemo } from 'react';
import React, { useMemo, useState, useEffect } from 'react';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { useApmParams } from '../../../hooks/use_apm_params';
import { useTimeRange } from '../../../hooks/use_time_range';
import { useTraceExplorerSamples } from '../../../hooks/use_trace_explorer_samples';
@ -16,7 +17,8 @@ export function TraceExplorerAggregatedCriticalPath() {
} = useApmParams('/traces/explorer/critical_path');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const [hasLoadedTable, setHasLoadedTable] = useState(false);
const { onPageReady } = usePerformanceContext();
const {
data: { traceSamples },
status: samplesFetchStatus,
@ -26,8 +28,24 @@ export function TraceExplorerAggregatedCriticalPath() {
return traceSamples.map((sample) => sample.traceId);
}, [traceSamples]);
useEffect(() => {
if (hasLoadedTable) {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
customMetrics: {
key1: 'traceIds',
value1: traceIds.length,
},
});
}
}, [hasLoadedTable, onPageReady, rangeFrom, rangeTo, traceIds]);
return (
<CriticalPathFlamegraph
onLoadTable={() => setHasLoadedTable(true)}
start={start}
end={end}
traceIds={traceIds}

View file

@ -7,6 +7,7 @@
import { FETCH_STATUS } from '@kbn/observability-shared-plugin/public';
import React, { useCallback, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { useApmParams } from '../../../hooks/use_apm_params';
import { useTimeRange } from '../../../hooks/use_time_range';
import { useTraceExplorerSamples } from '../../../hooks/use_trace_explorer_samples';
@ -18,7 +19,7 @@ import type { TransactionTab } from '../transaction_details/waterfall_with_summa
export function TraceExplorerWaterfall() {
const history = useHistory();
const { onPageReady } = usePerformanceContext();
const traceSamplesFetchResult = useTraceExplorerSamples();
const {
@ -55,6 +56,21 @@ export function TraceExplorerWaterfall() {
end,
});
useEffect(() => {
if (waterfallFetchResult.status === FETCH_STATUS.SUCCESS) {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
customMetrics: {
key1: 'traceDocsTotal',
value1: waterfallFetchResult.waterfall.traceDocsTotal,
},
});
}
}, [waterfallFetchResult, onPageReady, rangeFrom, rangeTo]);
const onSampleClick = useCallback(
(sample: any) => {
push(history, {

View file

@ -11,6 +11,7 @@ import type { XYBrushEvent } from '@elastic/charts';
import { EuiPanel, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui';
import { omit } from 'lodash';
import { useHistory } from 'react-router-dom';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { maybe } from '../../../../common/utils/maybe';
import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params';
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
@ -47,6 +48,7 @@ export function TransactionDetailsTabs() {
const isCriticalPathFeatureEnabled = useCriticalPathFeatureEnabledSetting();
const isTransactionProfilingEnabled = useTransactionProfilingSetting();
const { onPageReady } = usePerformanceContext();
const availableTabs = useMemo(() => {
const tabs = [traceSamplesTab, latencyCorrelationsTab, failedTransactionsCorrelationsTab];
@ -68,7 +70,7 @@ export function TransactionDetailsTabs() {
const { component: TabContent } =
availableTabs.find((tab) => tab.key === currentTab) ?? traceSamplesTab;
const { environment, kuery, transactionName } = query;
const { environment, kuery, transactionName, rangeFrom, rangeTo } = query;
const traceSamplesFetchResult = useTransactionTraceSamplesFetcher({
transactionName,
@ -90,6 +92,21 @@ export function TransactionDetailsTabs() {
setCurrentTab(traceSamplesTabKey);
}, [traceSamplesTabKey]);
useEffect(() => {
if (traceSamplesFetchResult.status === FETCH_STATUS.SUCCESS) {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
customMetrics: {
key1: 'traceDocsTotal',
value1: traceSamplesFetchResult.data?.traceSamples?.length ?? 0,
},
});
}
}, [traceSamplesFetchResult, onPageReady, rangeFrom, rangeTo]);
useEffect(() => {
const selectedSample = traceSamplesFetchResult.data?.traceSamples.find(
(sample) => sample.transactionId === transactionId && sample.traceId === traceId

View file

@ -6,8 +6,9 @@
*/
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui';
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { usePerformanceContext } from '@kbn/ebt-tools';
import { isServerlessAgentName } from '../../../../common/agent_name';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
@ -36,10 +37,22 @@ export function TransactionOverview() {
} = useApmParams('/services/{serviceName}/transactions');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const [hasLoadedTable, setHasLoadedTable] = useState(false);
const { onPageReady } = usePerformanceContext();
const { transactionType, fallbackToTransactions, serverlessType, serviceName } =
useApmServiceContext();
useEffect(() => {
if (hasLoadedTable) {
onPageReady({
meta: {
rangeFrom,
rangeTo,
},
});
}
}, [hasLoadedTable, onPageReady, rangeFrom, rangeTo]);
const history = useHistory();
// redirect to first transaction type
@ -109,6 +122,7 @@ export function TransactionOverview() {
hideViewTransactionsLink
numberOfTransactionsPerPage={10}
showMaxTransactionGroupsExceededWarning
onLoadTable={() => setHasLoadedTable(true)}
environment={environment}
kuery={kuery}
start={start}

View file

@ -25,6 +25,13 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { ApmTimeRangeMetadataContextProvider } from '../../../context/time_range_metadata/time_range_metadata_context';
import { MockTimeRangeContextProvider } from '../../../context/time_range_metadata/mock_time_range_metadata_context_provider';
// Mock the usePerformanceContext hook
jest.mock('@kbn/ebt-tools', () => ({
usePerformanceContext: () => ({
onPageReady: jest.fn(),
}),
}));
const KibanaReactContext = createKibanaReactContext({
uiSettings: { get: () => true },
usageCollection: { reportUiCounter: () => {} },

View file

@ -10,9 +10,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, euiPaletteColorBlind } fr
import { css } from '@emotion/css';
import { useChartThemes } from '@kbn/observability-shared-plugin/public';
import { uniqueId } from 'lodash';
import React, { useMemo, useRef } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
import type { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { useFetcher, isPending } from '../../../hooks/use_fetcher';
import { CriticalPathFlamegraphTooltip } from './critical_path_flamegraph_tooltip';
import { criticalPathToFlamegraph } from './critical_path_to_flamegraph';
@ -27,9 +27,10 @@ export function CriticalPathFlamegraph(
end: string;
traceIds: string[];
traceIdsFetchStatus: FETCH_STATUS;
onLoadTable?: () => void;
} & ({ serviceName: string; transactionName: string } | {})
) {
const { start, end, traceIds, traceIdsFetchStatus } = props;
const { start, end, traceIds, traceIdsFetchStatus, onLoadTable } = props;
const serviceName = 'serviceName' in props ? props.serviceName : null;
const transactionName = 'transactionName' in props ? props.transactionName : null;
@ -40,6 +41,7 @@ export function CriticalPathFlamegraph(
// of the search.
const timerange = useRef({ start, end });
timerange.current = { start, end };
const [hasTableLoaded, setHasTableLoaded] = useState(false);
const { data: { criticalPath } = { criticalPath: null }, status: criticalPathFetchStatus } =
useFetcher(
@ -63,6 +65,24 @@ export function CriticalPathFlamegraph(
[timerange, traceIds, serviceName, transactionName]
);
useEffect(() => {
if (
criticalPathFetchStatus === FETCH_STATUS.SUCCESS &&
traceIdsFetchStatus === FETCH_STATUS.SUCCESS &&
onLoadTable &&
!hasTableLoaded
) {
onLoadTable();
setHasTableLoaded(true);
}
}, [
criticalPathFetchStatus,
onLoadTable,
hasTableLoaded,
traceIdsFetchStatus,
setHasTableLoaded,
]);
const chartThemes = useChartThemes();
const isLoading = isPending(traceIdsFetchStatus) || isPending(criticalPathFetchStatus);

View file

@ -55,6 +55,7 @@ interface Props {
end: string;
saveTableOptionsToUrl?: boolean;
showSparkPlots?: boolean;
onLoadTable?: () => void;
}
export function TransactionsTable({
@ -69,6 +70,7 @@ export function TransactionsTable({
start,
end,
saveTableOptionsToUrl = false,
onLoadTable,
showSparkPlots,
}: Props) {
const { link } = useApmRouter();
@ -89,7 +91,7 @@ export function TransactionsTable({
const shouldShowSparkPlots = showSparkPlots ?? !isLarge;
const { transactionType, serviceName } = useApmServiceContext();
const [searchQuery, setSearchQueryDebounced] = useStateDebounced('');
const [hasTableLoaded, setHasTableLoaded] = useState(false);
const [renderedItems, setRenderedItems] = useState<ApiResponse['transactionGroups']>([]);
const { mainStatistics, mainStatisticsStatus, detailedStatistics, detailedStatisticsStatus } =
@ -107,6 +109,18 @@ export function TransactionsTable({
transactionType,
});
useEffect(() => {
if (
mainStatisticsStatus === FETCH_STATUS.SUCCESS &&
detailedStatisticsStatus === FETCH_STATUS.SUCCESS &&
onLoadTable &&
!hasTableLoaded
) {
onLoadTable();
setHasTableLoaded(true);
}
}, [mainStatisticsStatus, detailedStatisticsStatus, onLoadTable, hasTableLoaded]);
const columns = useMemo(() => {
return getColumns({
serviceName,