From d61eb1d6273bb86dc2563329864497ff8f882bdc Mon Sep 17 00:00:00 2001 From: Kate Patticha Date: Tue, 14 Jan 2025 19:10:08 +0200 Subject: [PATCH 01/18] [Perfomance] Add `is_initial_load` meta --- .../src/track_performance_measure_entries.ts | 1 + .../context/measure_interaction/index.ts | 29 ++++++++--------- .../measure_interaction.test.tsx | 31 ++++++++++++++++--- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts index fc46230f85c2c..4067e100263cb 100644 --- a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts +++ b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts @@ -75,6 +75,7 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev target, query_range_secs: meta?.queryRangeSecs, query_offset_secs: meta?.queryOffsetSecs, + is_initial_load: meta?.isInitialLoad, }, }); } catch (error) { diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts index 790314a8f0892..56d58964206c8 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts @@ -18,11 +18,13 @@ import { EventData } from '../performance_context'; interface PerformanceMeta { queryRangeSecs: number; queryOffsetSecs: number; + isInitialLoad: boolean; } export function measureInteraction() { performance.mark(perfomanceMarkers.startPageChange); - const trackedRoutes: string[] = []; + const trackedRoutes = new Set(); + return { /** * Marks the end of the page ready state and measures the performance between the start of the page change and the end of the page ready state. @@ -46,22 +48,21 @@ export function measureInteraction() { queryRangeSecs: getTimeDifferenceInSeconds(dateRangesInEpoch), queryOffsetSecs: rangeTo === 'now' ? 0 : getOffsetFromNowInSeconds(dateRangesInEpoch.endDate), + isInitialLoad: !trackedRoutes.has(pathname), }; } - if (!trackedRoutes.includes(pathname)) { - performance.measure(pathname, { - detail: { - eventName: 'kibana:plugin_render_time', - type: 'kibana:performance', - customMetrics: eventData?.customMetrics, - meta: performanceMeta, - }, - start: perfomanceMarkers.startPageChange, - end: perfomanceMarkers.endPageReady, - }); - trackedRoutes.push(pathname); - } + performance.measure(pathname, { + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: eventData?.customMetrics, + meta: performanceMeta, + }, + start: perfomanceMarkers.startPageChange, + end: perfomanceMarkers.endPageReady, + }); + trackedRoutes.add(pathname); }, }; } diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx index 5768a6126c571..fb17d7ede35dd 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx @@ -61,6 +61,7 @@ describe('measureInteraction', () => { meta: { queryRangeSecs: 900, queryOffsetSecs: 0, + isInitialLoad: true, }, }, end: 'end::pageReady', @@ -88,6 +89,7 @@ describe('measureInteraction', () => { meta: { queryRangeSecs: 1800, queryOffsetSecs: 0, + isInitialLoad: true, }, }, end: 'end::pageReady', @@ -115,6 +117,7 @@ describe('measureInteraction', () => { meta: { queryRangeSecs: 86400, queryOffsetSecs: -1800, + isInitialLoad: true, }, }, end: 'end::pageReady', @@ -142,6 +145,7 @@ describe('measureInteraction', () => { meta: { queryRangeSecs: 86400, queryOffsetSecs: 1800, + isInitialLoad: true, }, }, end: 'end::pageReady', @@ -149,13 +153,32 @@ describe('measureInteraction', () => { }); }); - it('should not measure the same route twice', () => { + it('should set isInitialLoad to false on subsequent pageReady calls', () => { const interaction = measureInteraction(); const pathname = '/test-path'; + jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z - interaction.pageReady(pathname); - interaction.pageReady(pathname); + const eventData = { + meta: { rangeFrom: '2024-12-08T01:00:00Z', rangeTo: '2024-12-09T01:00:00Z' }, + }; - expect(performance.measure).toHaveBeenCalledTimes(1); + interaction.pageReady(pathname, eventData); + interaction.pageReady(pathname, eventData); + + expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); + expect(performance.measure).toHaveBeenCalledWith(pathname, { + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: undefined, + meta: { + queryRangeSecs: 86400, + queryOffsetSecs: 1800, + isInitialLoad: false, + }, + }, + end: 'end::pageReady', + start: 'start::pageChange', + }); }); }); From 4d6fde0d099263afaaa3de167d6210f4aabf3849 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Thu, 13 Feb 2025 18:48:24 +0200 Subject: [PATCH 02/18] [APM] Refactor ttmp instrumentation --- .../error_group_list/index.tsx | 19 ++++------ .../service_overview/apm_overview/index.tsx | 20 ++++++---- .../index.tsx | 19 +++++----- .../service_overview_errors_table/index.tsx | 3 +- ...race_explorer_aggregated_critical_path.tsx | 22 +---------- .../app/transaction_overview/index.tsx | 14 ------- .../shared/critical_path_flamegraph/index.tsx | 38 +++++++++---------- .../shared/transactions_table/index.tsx | 24 ++++++++---- .../shared/unified_search_bar/index.tsx | 3 ++ 9 files changed, 70 insertions(+), 92 deletions(-) diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx index 32c69c90d0d2d..fd6c528c1a450 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx @@ -8,10 +8,10 @@ import { EuiBadge, EuiIconTip, EuiToolTip, RIGHT_ALIGNMENT } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from '@emotion/styled'; -import React, { useEffect, useMemo, useState, useRef } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { apmEnableTableSearchBar } from '@kbn/observability-plugin/common'; import { usePerformanceContext } from '@kbn/ebt-tools'; -import { FETCH_STATUS, isPending } from '../../../../hooks/use_fetcher'; +import { isPending, isSuccess } from '../../../../hooks/use_fetcher'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { asBigNumber } from '../../../../../common/utils/formatters'; import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; @@ -27,6 +27,7 @@ import { isTimeComparison } from '../../../shared/time_comparison/get_comparison import type { ErrorGroupItem } from './use_error_group_list_data'; import { useErrorGroupListData } from './use_error_group_list_data'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import type { TablesLoadedState } from '../../service_overview/apm_overview'; const GroupIdLink = styled(ErrorDetailLink)` font-family: ${({ theme }) => theme.euiTheme.font.familyCode}; @@ -56,7 +57,7 @@ interface Props { comparisonEnabled?: boolean; saveTableOptionsToUrl?: boolean; showPerPageOptions?: boolean; - onLoadTable?: () => void; + onLoadTable?: (key: keyof TablesLoadedState) => void; } const defaultSorting = { @@ -85,7 +86,6 @@ export function ErrorGroupList({ const { offset, rangeFrom, rangeTo } = query; const [renderedItems, setRenderedItems] = useState([]); - const hasTableLoaded = useRef(false); const [sorting, setSorting] = useState['sort']>(defaultSorting); const { @@ -103,13 +103,9 @@ export function ErrorGroupList({ 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 (isSuccess(mainStatisticsStatus) && isSuccess(detailedStatisticsStatus)) { if (onLoadTable) { - onLoadTable(); + onLoadTable('errors'); } else { onPageReady({ meta: { @@ -118,15 +114,14 @@ export function ErrorGroupList({ }, }); } - hasTableLoaded.current = true; } }, [ mainStatisticsStatus, detailedStatisticsStatus, - onLoadTable, rangeFrom, rangeTo, onPageReady, + onLoadTable, ]); const columns = useMemo(() => { diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/apm_overview/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/apm_overview/index.tsx index f35174f39ee15..e5813f81a2cd7 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/apm_overview/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/apm_overview/index.tsx @@ -7,7 +7,7 @@ import type { EuiFlexGroupProps } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiPanel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { usePerformanceContext } from '@kbn/ebt-tools'; import { chartHeight } from '..'; import type { AgentName } from '../../../../../typings/es_schemas/ui/fields/agent'; @@ -36,6 +36,12 @@ import { useLocalStorage } from '../../../../hooks/use_local_storage'; const latencyChartHeight = 200; +export interface TablesLoadedState { + transactions: boolean; + dependencies: boolean; + errors: boolean; +} + export function ApmOverview() { const router = useApmRouter(); const { serviceName, fallbackToTransactions, agentName, serverlessType } = useApmServiceContext(); @@ -45,7 +51,7 @@ export function ApmOverview() { } = useApmParams('/services/{serviceName}/overview'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const [haveTablesLoaded, setHaveTablesLoaded] = useState({ + const [haveTablesLoaded, setHaveTablesLoaded] = useState({ transactions: false, dependencies: false, errors: false, @@ -81,9 +87,9 @@ export function ApmOverview() { false ); - const onLoadTable = (key: string) => { + const handleOnLoadTable = useCallback((key: keyof TablesLoadedState) => { setHaveTablesLoaded((currentValues) => ({ ...currentValues, [key]: true })); - }; + }, []); return ( <> @@ -121,7 +127,7 @@ export function ApmOverview() { kuery={kuery} environment={environment} fixedHeight={true} - onLoadTable={() => onLoadTable('transactions')} + onLoadTable={handleOnLoadTable} start={start} end={end} showPerPageOptions={false} @@ -147,7 +153,7 @@ export function ApmOverview() { onLoadTable('errors')} + onLoadTable={handleOnLoadTable} /> @@ -178,7 +184,7 @@ export function ApmOverview() { onLoadTable('dependencies')} + onLoadTable={handleOnLoadTable} fixedHeight={true} showPerPageOptions={false} link={ diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index 3d7889ac0ef49..68290f8223678 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -9,25 +9,26 @@ import { EuiIconTip } from '@elastic/eui'; import { METRIC_TYPE } from '@kbn/analytics'; import { i18n } from '@kbn/i18n'; import type { ReactNode } from 'react'; -import React, { useEffect, useRef } from 'react'; -import { FETCH_STATUS, useUiTracker } from '@kbn/observability-shared-plugin/public'; +import React, { useEffect } from '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 { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../../hooks/use_apm_params'; -import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useFetcher, isSuccess } from '../../../../hooks/use_fetcher'; import { useTimeRange } from '../../../../hooks/use_time_range'; import { DependencyLink } from '../../../shared/links/dependency_link'; import { DependenciesTable } from '../../../shared/dependencies_table'; import { ServiceLink } from '../../../shared/links/apm/service_link'; +import type { TablesLoadedState } from '../apm_overview'; interface ServiceOverviewDependenciesTableProps { fixedHeight?: boolean; link?: ReactNode; showPerPageOptions?: boolean; showSparkPlots?: boolean; - onLoadTable?: () => void; + onLoadTable?: (key: keyof TablesLoadedState) => void; } export function ServiceOverviewDependenciesTable({ @@ -55,7 +56,6 @@ export function ServiceOverviewDependenciesTable({ const { serviceName, transactionType } = useApmServiceContext(); const { onPageReady } = usePerformanceContext(); const trackEvent = useUiTracker(); - const hasTableLoaded = useRef(false); const { data, status } = useFetcher( (callApmApi) => { if (!start || !end) { @@ -79,11 +79,11 @@ export function ServiceOverviewDependenciesTable({ ); useEffect(() => { - // this component is used both for the service overview tab and the transactions tab, + // this component is used both for the service overview tab and the dependency tab, // onLoadTable will be defined if it's the service overview tab - if (status === FETCH_STATUS.SUCCESS && !hasTableLoaded.current) { + if (isSuccess(status)) { if (onLoadTable) { - onLoadTable(); + onLoadTable('dependencies'); } else { onPageReady({ meta: { @@ -92,9 +92,8 @@ export function ServiceOverviewDependenciesTable({ }, }); } - hasTableLoaded.current = true; } - }, [status, onLoadTable, onPageReady, rangeFrom, rangeTo]); + }, [status, onPageReady, rangeFrom, rangeTo, onLoadTable]); const dependencies = data?.serviceDependencies.map((dependency) => { diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index bbe0c4a901cec..f637f5f3827d0 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -12,10 +12,11 @@ import { useApmParams } from '../../../../hooks/use_apm_params'; import { OverviewTableContainer } from '../../../shared/overview_table_container'; import { ErrorOverviewLink } from '../../../shared/links/apm/error_overview_link'; import { ErrorGroupList } from '../../error_group_overview/error_group_list'; +import type { TablesLoadedState } from '../apm_overview'; interface Props { serviceName: string; - onLoadTable?: () => void; + onLoadTable?: (key: keyof TablesLoadedState) => void; } export function ServiceOverviewErrorsTable({ serviceName, onLoadTable }: Props) { diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx index f69f28c038377..f4e804e9c5b48 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useMemo, useState, useEffect } from 'react'; -import { usePerformanceContext } from '@kbn/ebt-tools'; +import React, { useMemo } from 'react'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useTimeRange } from '../../../hooks/use_time_range'; import { useTraceExplorerSamples } from '../../../hooks/use_trace_explorer_samples'; @@ -17,8 +16,7 @@ export function TraceExplorerAggregatedCriticalPath() { } = useApmParams('/traces/explorer/critical_path'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const [hasLoadedTable, setHasLoadedTable] = useState(false); - const { onPageReady } = usePerformanceContext(); + // const [hasLoadedTable, setHasLoadedTable] = useState(false); const { data: { traceSamples }, status: samplesFetchStatus, @@ -28,24 +26,8 @@ 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 ( setHasLoadedTable(true)} start={start} end={end} traceIds={traceIds} diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx index 5c2c499a0f7b8..6ff0ee97f51f8 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -37,22 +37,9 @@ 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 @@ -122,7 +109,6 @@ export function TransactionOverview() { hideViewTransactionsLink numberOfTransactionsPerPage={10} showMaxTransactionGroupsExceededWarning - onLoadTable={() => setHasLoadedTable(true)} environment={environment} kuery={kuery} start={start} diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx index ddb25d92fd255..e4c4d592292b4 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx @@ -10,9 +10,11 @@ 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, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { i18n } from '@kbn/i18n'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { usePerformanceContext } from '@kbn/ebt-tools'; +import type { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { isSuccess } 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,10 +29,10 @@ export function CriticalPathFlamegraph( end: string; traceIds: string[]; traceIdsFetchStatus: FETCH_STATUS; - onLoadTable?: () => void; } & ({ serviceName: string; transactionName: string } | {}) ) { - const { start, end, traceIds, traceIdsFetchStatus, onLoadTable } = props; + const { start, end, traceIds, traceIdsFetchStatus } = props; + const { onPageReady } = usePerformanceContext(); const serviceName = 'serviceName' in props ? props.serviceName : null; const transactionName = 'transactionName' in props ? props.transactionName : null; @@ -41,7 +43,6 @@ 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( @@ -66,22 +67,19 @@ export function CriticalPathFlamegraph( ); useEffect(() => { - if ( - criticalPathFetchStatus === FETCH_STATUS.SUCCESS && - traceIdsFetchStatus === FETCH_STATUS.SUCCESS && - onLoadTable && - !hasTableLoaded - ) { - onLoadTable(); - setHasTableLoaded(true); + if (isSuccess(criticalPathFetchStatus) && isSuccess(traceIdsFetchStatus)) { + onPageReady({ + meta: { + rangeFrom: start, + rangeTo: end, + }, + customMetrics: { + key1: 'traceIds', + value1: traceIds.length, + }, + }); } - }, [ - criticalPathFetchStatus, - onLoadTable, - hasTableLoaded, - traceIdsFetchStatus, - setHasTableLoaded, - ]); + }, [start, end, criticalPathFetchStatus, traceIdsFetchStatus, traceIds, onPageReady]); const chartThemes = useChartThemes(); diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx index 75703b3298332..5c5e53c9077f5 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -8,6 +8,7 @@ import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { v4 as uuidv4 } from 'uuid'; +import { usePerformanceContext } from '@kbn/ebt-tools'; import { FormattedMessage } from '@kbn/i18n-react'; import { compact } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; @@ -30,6 +31,7 @@ import { OverviewTableContainer } from '../overview_table_container'; import { isTimeComparison } from '../time_comparison/get_comparison_options'; import { getColumns } from './get_columns'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import type { TablesLoadedState } from '../../app/service_overview/apm_overview'; type ApiResponse = APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>; @@ -55,7 +57,7 @@ interface Props { end: string; saveTableOptionsToUrl?: boolean; showSparkPlots?: boolean; - onLoadTable?: () => void; + onLoadTable?: (key: keyof TablesLoadedState) => void; } export function TransactionsTable({ @@ -91,8 +93,8 @@ export function TransactionsTable({ const shouldShowSparkPlots = showSparkPlots ?? !isLarge; const { transactionType, serviceName } = useApmServiceContext(); const [searchQuery, setSearchQueryDebounced] = useStateDebounced(''); - const [hasTableLoaded, setHasTableLoaded] = useState(false); const [renderedItems, setRenderedItems] = useState([]); + const { onPageReady } = usePerformanceContext(); const { mainStatistics, mainStatisticsStatus, detailedStatistics, detailedStatisticsStatus } = useTableData({ @@ -112,14 +114,20 @@ export function TransactionsTable({ useEffect(() => { if ( mainStatisticsStatus === FETCH_STATUS.SUCCESS && - detailedStatisticsStatus === FETCH_STATUS.SUCCESS && - onLoadTable && - !hasTableLoaded + detailedStatisticsStatus === FETCH_STATUS.SUCCESS ) { - onLoadTable(); - setHasTableLoaded(true); + if (onLoadTable) { + onLoadTable('transactions'); + } else { + onPageReady({ + meta: { + rangeFrom: start, + rangeTo: end, + }, + }); + } } - }, [mainStatisticsStatus, detailedStatisticsStatus, onLoadTable, hasTableLoaded]); + }, [mainStatisticsStatus, detailedStatisticsStatus, onLoadTable, end, start, onPageReady]); const columns = useMemo(() => { return getColumns({ diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/index.tsx index ac2b67b8b9c94..c59de3f9d747e 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/index.tsx @@ -13,6 +13,7 @@ import { toElasticsearchQuery, } from '@kbn/es-query'; import { useHistory, useLocation } from 'react-router-dom'; +import { usePerformanceContext } from '@kbn/ebt-tools'; import deepEqual from 'fast-deep-equal'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import qs from 'query-string'; @@ -141,6 +142,7 @@ export function UnifiedSearchBar({ const { kuery, serviceName, environment, groupId, refreshPausedFromUrl, refreshIntervalFromUrl } = useSearchBarParams(value); + const { markPerformanceRefreshStart } = usePerformanceContext(); const timePickerTimeDefaults = core.uiSettings.get( UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS ); @@ -204,6 +206,7 @@ export function UnifiedSearchBar({ const onRefresh = () => { clearCache(); incrementTimeRangeId(); + markPerformanceRefreshStart(); }; const onRefreshChange = ({ isPaused, refreshInterval }: Partial) => { From 4e914ccb186fa4e0b4bc51f6c907295e7b1e854f Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Fri, 14 Feb 2025 11:49:05 +0200 Subject: [PATCH 03/18] [Performance] Add ability to track ttfmp on refresh --- .../context/measure_interaction/index.ts | 60 +++++++++++++------ .../context/performance_context.tsx | 11 +++- .../context/use_performance_context.tsx | 13 ++++ .../performance_markers.ts | 1 + .../app/transaction_overview/index.tsx | 3 +- 5 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts index 56d58964206c8..ea856cdfce564 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts @@ -12,18 +12,17 @@ import { getOffsetFromNowInSeconds, getTimeDifferenceInSeconds, } from '@kbn/timerange'; -import { perfomanceMarkers } from '../../performance_markers'; import { EventData } from '../performance_context'; +import { perfomanceMarkers } from '../../performance_markers'; interface PerformanceMeta { queryRangeSecs: number; queryOffsetSecs: number; - isInitialLoad: boolean; + isInitialLoad?: boolean; } -export function measureInteraction() { +export function measureInteraction(pathname: string) { performance.mark(perfomanceMarkers.startPageChange); - const trackedRoutes = new Set(); return { /** @@ -31,7 +30,7 @@ export function measureInteraction() { * @param pathname - The pathname of the page. * @param customMetrics - Custom metrics to be included in the performance measure. */ - pageReady(pathname: string, eventData?: EventData) { + pageReady(eventData?: EventData) { let performanceMeta: PerformanceMeta | undefined; performance.mark(perfomanceMarkers.endPageReady); @@ -48,21 +47,48 @@ export function measureInteraction() { queryRangeSecs: getTimeDifferenceInSeconds(dateRangesInEpoch), queryOffsetSecs: rangeTo === 'now' ? 0 : getOffsetFromNowInSeconds(dateRangesInEpoch.endDate), - isInitialLoad: !trackedRoutes.has(pathname), }; } - performance.measure(pathname, { - detail: { - eventName: 'kibana:plugin_render_time', - type: 'kibana:performance', - customMetrics: eventData?.customMetrics, - meta: performanceMeta, - }, - start: perfomanceMarkers.startPageChange, - end: perfomanceMarkers.endPageReady, - }); - trackedRoutes.add(pathname); + if ( + performance.getEntriesByName(perfomanceMarkers.startPageChange).length > 0 && + performance.getEntriesByName(perfomanceMarkers.endPageReady).length > 0 + ) { + performance.measure(`[ttfmp:initial] - ${pathname}`, { + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: eventData?.customMetrics, + meta: { ...performanceMeta, isInitialLoad: true }, + }, + start: perfomanceMarkers.startPageChange, + end: perfomanceMarkers.endPageReady, + }); + + // Clean up the marks once the measure is done + performance.clearMarks(perfomanceMarkers.startPageChange); + performance.clearMarks(perfomanceMarkers.endPageReady); + } + + if ( + performance.getEntriesByName(perfomanceMarkers.startPageRefresh).length > 0 && + performance.getEntriesByName(perfomanceMarkers.endPageReady).length > 0 + ) { + performance.measure(`[ttfmp:refresh] - ${pathname}`, { + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: eventData?.customMetrics, + meta: { ...performanceMeta, isInitialLoad: false }, + }, + start: perfomanceMarkers.startPageRefresh, + end: perfomanceMarkers.endPageReady, + }); + + // // Clean up the marks once the measure is done + performance.clearMarks(perfomanceMarkers.startPageRefresh); + performance.clearMarks(perfomanceMarkers.endPageReady); + } }, }; } diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx index c3e60270c6ac4..4e563ee10b025 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx @@ -13,6 +13,7 @@ import { useLocation } from 'react-router-dom'; import { PerformanceApi, PerformanceContext } from './use_performance_context'; import { PerformanceMetricEvent } from '../../performance_metric_events'; import { measureInteraction } from './measure_interaction'; +import { perfomanceMarkers } from '../performance_markers'; export type CustomMetrics = Omit; @@ -28,7 +29,8 @@ export interface EventData { export function PerformanceContextProvider({ children }: { children: React.ReactElement }) { const [isRendered, setIsRendered] = useState(false); const location = useLocation(); - const interaction = measureInteraction(); + + const interaction = useMemo(() => measureInteraction(location.pathname), [location.pathname]); React.useEffect(() => { afterFrame(() => { @@ -44,11 +46,14 @@ export function PerformanceContextProvider({ children }: { children: React.React () => ({ onPageReady(eventData) { if (isRendered) { - interaction.pageReady(location.pathname, eventData); + interaction.pageReady(eventData); } }, + markPerformanceRefreshStart() { + performance.mark(perfomanceMarkers.startPageRefresh); + }, }), - [isRendered, location.pathname, interaction] + [isRendered, interaction] ); return {children}; diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx index a2fab435778e1..bd664622c7c2d 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx @@ -15,6 +15,19 @@ export interface PerformanceApi { * @param eventData - Data to send with the performance measure, conforming the structure of a {@link EventData}. */ onPageReady(eventData?: EventData): void; + /** + * Marks the start of a page refresh event for performance tracking. + * This method adds a performance marker start::pageRefresh to indicate when a page refresh begins. + * + * Usage: + * ```ts + * markPerformanceRefreshStart(); + * ``` + * + * The marker set by this function can later be used in performance measurements + * along with an end marker end::pageReady to determine the total refresh duration. + */ + markPerformanceRefreshStart(): void; } export const PerformanceContext = createContext(undefined); diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/performance_markers.ts b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/performance_markers.ts index 75a807e33b5c8..f5d9aef03135e 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/performance_markers.ts +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/performance_markers.ts @@ -11,4 +11,5 @@ export const perfomanceMarkers = { startPageChange: 'start::pageChange', endPageReady: 'end::pageReady', + startPageRefresh: 'start::pageRefresh', }; diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx index 6ff0ee97f51f8..209adb42a4129 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -6,9 +6,8 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect } 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'; From 08662d78f7a4ba6e6cdf24a13834b9bbee695e39 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Fri, 14 Feb 2025 11:49:14 +0200 Subject: [PATCH 04/18] Remove target --- .../browser-internal/src/track_performance_measure_entries.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts index 4067e100263cb..42b0e93aa8789 100644 --- a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts +++ b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts @@ -72,7 +72,6 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev duration, ...customMetrics, meta: { - target, query_range_secs: meta?.queryRangeSecs, query_offset_secs: meta?.queryOffsetSecs, is_initial_load: meta?.isInitialLoad, From 7302198e4fdd676d999c8d783c12ec3212881ad3 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Fri, 14 Feb 2025 12:19:37 +0200 Subject: [PATCH 05/18] [Infra] Track tttmp on refresh --- .../hosts/components/search_bar/unified_search_bar.tsx | 5 ++++- .../metrics_explorer/hooks/use_metric_explorer_state.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx index 784c7679a5838..63793e5cde1e2 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx @@ -8,6 +8,7 @@ import React, { useCallback } from 'react'; import type { TimeRange } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; +import { usePerformanceContext } from '@kbn/ebt-tools'; import { useEuiTheme, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { css } from '@emotion/react'; import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana'; @@ -24,6 +25,7 @@ export const UnifiedSearchBar = () => { const { metricsView } = useMetricsDataViewContext(); const { searchCriteria, onLimitChange, onPanelFiltersChange, onSubmit } = useUnifiedSearchContext(); + const { markPerformanceRefreshStart } = usePerformanceContext(); const { SearchBar } = unifiedSearch.ui; @@ -32,9 +34,10 @@ export const UnifiedSearchBar = () => { // This makes sure `onSubmit` is only called when the submit button is clicked if (isUpdate === false) { onSubmit(payload); + markPerformanceRefreshStart(); } }, - [onSubmit] + [onSubmit, markPerformanceRefreshStart] ); return ( diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts index 019936f66be85..c19f9e0ccdfc6 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts @@ -7,6 +7,7 @@ import DateMath from '@kbn/datemath'; import { useCallback, useEffect } from 'react'; +import { usePerformanceContext } from '@kbn/ebt-tools'; import type { MetricsExplorerChartOptions, MetricsExplorerOptions, @@ -35,6 +36,7 @@ export const useMetricsExplorerState = ({ enabled }: { enabled: boolean } = { en timestamps, setTimestamps, } = useMetricsExplorerOptionsContainerContext(); + const { markPerformanceRefreshStart } = usePerformanceContext(); const refreshTimestamps = useCallback(() => { const fromTimestamp = DateMath.parse(timeRange.from)!.valueOf(); @@ -45,7 +47,8 @@ export const useMetricsExplorerState = ({ enabled }: { enabled: boolean } = { en fromTimestamp, toTimestamp, }); - }, [setTimestamps, timeRange]); + markPerformanceRefreshStart(); + }, [setTimestamps, timeRange, markPerformanceRefreshStart]); const { data, error, fetchNextPage, isLoading } = useMetricsExplorerData({ options, From 427f1a262ef355d0c6be96f71162cca7541a7ae9 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Fri, 14 Feb 2025 13:02:40 +0200 Subject: [PATCH 06/18] clean up --- .../trace_explorer/trace_explorer_aggregated_critical_path.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx index f4e804e9c5b48..2059b0c27d748 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx @@ -16,7 +16,6 @@ export function TraceExplorerAggregatedCriticalPath() { } = useApmParams('/traces/explorer/critical_path'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - // const [hasLoadedTable, setHasLoadedTable] = useState(false); const { data: { traceSamples }, status: samplesFetchStatus, From ae7659f156f805c997bc705c3b443bec2aeb4e71 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Fri, 14 Feb 2025 16:51:57 +0200 Subject: [PATCH 07/18] Keep the logic in parent component --- .../error_group_overview/error_group_list/index.tsx | 5 ++--- .../app/service_overview/apm_overview/index.tsx | 13 ++++++++----- .../service_overview_errors_table/index.tsx | 3 +-- .../components/shared/transactions_table/index.tsx | 5 ++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx index fd6c528c1a450..258a50b2c3405 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/error_group_overview/error_group_list/index.tsx @@ -27,7 +27,6 @@ import { isTimeComparison } from '../../../shared/time_comparison/get_comparison import type { ErrorGroupItem } from './use_error_group_list_data'; import { useErrorGroupListData } from './use_error_group_list_data'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; -import type { TablesLoadedState } from '../../service_overview/apm_overview'; const GroupIdLink = styled(ErrorDetailLink)` font-family: ${({ theme }) => theme.euiTheme.font.familyCode}; @@ -57,7 +56,7 @@ interface Props { comparisonEnabled?: boolean; saveTableOptionsToUrl?: boolean; showPerPageOptions?: boolean; - onLoadTable?: (key: keyof TablesLoadedState) => void; + onLoadTable?: () => void; } const defaultSorting = { @@ -105,7 +104,7 @@ export function ErrorGroupList({ // onLoadTable will be defined if it's the service overview tab if (isSuccess(mainStatisticsStatus) && isSuccess(detailedStatisticsStatus)) { if (onLoadTable) { - onLoadTable('errors'); + onLoadTable(); } else { onPageReady({ meta: { diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/apm_overview/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/apm_overview/index.tsx index e5813f81a2cd7..24a96ff5124ee 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/apm_overview/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/apm_overview/index.tsx @@ -87,9 +87,12 @@ export function ApmOverview() { false ); - const handleOnLoadTable = useCallback((key: keyof TablesLoadedState) => { + const handleOnLoadTable = (key: keyof TablesLoadedState) => setHaveTablesLoaded((currentValues) => ({ ...currentValues, [key]: true })); - }, []); + + const onTransactionsTableLoad = useCallback(() => handleOnLoadTable('transactions'), []); + const onErrorsTableLoad = useCallback(() => handleOnLoadTable('errors'), []); + const onDependenciesTableLoad = useCallback(() => handleOnLoadTable('dependencies'), []); return ( <> @@ -127,7 +130,7 @@ export function ApmOverview() { kuery={kuery} environment={environment} fixedHeight={true} - onLoadTable={handleOnLoadTable} + onLoadTable={onTransactionsTableLoad} start={start} end={end} showPerPageOptions={false} @@ -153,7 +156,7 @@ export function ApmOverview() { @@ -184,7 +187,7 @@ export function ApmOverview() { void; + onLoadTable?: () => void; } export function ServiceOverviewErrorsTable({ serviceName, onLoadTable }: Props) { diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx index 5c5e53c9077f5..a562ceadc1aeb 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -31,7 +31,6 @@ import { OverviewTableContainer } from '../overview_table_container'; import { isTimeComparison } from '../time_comparison/get_comparison_options'; import { getColumns } from './get_columns'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; -import type { TablesLoadedState } from '../../app/service_overview/apm_overview'; type ApiResponse = APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>; @@ -57,7 +56,7 @@ interface Props { end: string; saveTableOptionsToUrl?: boolean; showSparkPlots?: boolean; - onLoadTable?: (key: keyof TablesLoadedState) => void; + onLoadTable?: () => void; } export function TransactionsTable({ @@ -117,7 +116,7 @@ export function TransactionsTable({ detailedStatisticsStatus === FETCH_STATUS.SUCCESS ) { if (onLoadTable) { - onLoadTable('transactions'); + onLoadTable(); } else { onPageReady({ meta: { From 4fca5926ff341522f230e0255831debfd28b48a1 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Fri, 14 Feb 2025 17:20:46 +0200 Subject: [PATCH 08/18] Address PR comments --- ...race_explorer_aggregated_critical_path.tsx | 17 +++++++++++++- .../aggregated_critical_path_tab.tsx | 19 +++++++++++++++- .../app/transaction_overview/index.tsx | 15 ++++++++++++- .../shared/critical_path_flamegraph/index.tsx | 18 ++++----------- .../shared/transactions_table/index.tsx | 22 ++++--------------- 5 files changed, 56 insertions(+), 35 deletions(-) diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx index 2059b0c27d748..dae5a7223dca6 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx @@ -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, useCallback } 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'; @@ -20,17 +21,31 @@ export function TraceExplorerAggregatedCriticalPath() { data: { traceSamples }, status: samplesFetchStatus, } = useTraceExplorerSamples(); + const { onPageReady } = usePerformanceContext(); const traceIds = useMemo(() => { return traceSamples.map((sample) => sample.traceId); }, [traceSamples]); + const handleOnLoadTable = useCallback(() => { + onPageReady({ + meta: { + rangeFrom: start, + rangeTo: end, + }, + customMetrics: { + key1: 'traceIds', + value1: traceIds.length, + }, + }); + }, []); return ( ); } diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_details/aggregated_critical_path_tab.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_details/aggregated_critical_path_tab.tsx index 915f8eaf38610..aa37e6ad0b97c 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_details/aggregated_critical_path_tab.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_details/aggregated_critical_path_tab.tsx @@ -7,7 +7,8 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; +import { usePerformanceContext } from '@kbn/ebt-tools'; import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; import { useTimeRange } from '../../../hooks/use_time_range'; import { CriticalPathFlamegraph } from '../../shared/critical_path_flamegraph'; @@ -23,12 +24,27 @@ function TransactionDetailAggregatedCriticalPath({ traceSamplesFetchResult }: Ta '/mobile-services/{serviceName}/transactions/view' ); + const { onPageReady } = usePerformanceContext(); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); const traceIds = useMemo(() => { return traceSamplesFetchResult.data?.traceSamples.map((sample) => sample.traceId) ?? []; }, [traceSamplesFetchResult.data]); + const handleOnLoadTable = useCallback(() => { + onPageReady({ + meta: { + rangeFrom: start, + rangeTo: end, + }, + customMetrics: { + key1: 'traceIds', + value1: traceIds.length, + }, + }); + }, []); + return ( ); } diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx index 209adb42a4129..f6d9e73824c56 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -6,7 +6,9 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; -import React, { useEffect } from 'react'; +import { usePerformanceContext } from '@kbn/ebt-tools'; + +import React, { useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { isServerlessAgentName } from '../../../../common/agent_name'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; @@ -38,6 +40,7 @@ export function TransactionOverview() { const { start, end } = useTimeRange({ rangeFrom, rangeTo }); const { transactionType, fallbackToTransactions, serverlessType, serviceName } = useApmServiceContext(); + const { onPageReady } = usePerformanceContext(); const history = useHistory(); @@ -70,6 +73,15 @@ export function TransactionOverview() { return ; } + const handleOnLoadTable = useCallback(() => { + onPageReady({ + meta: { + rangeFrom: start, + rangeTo: end, + }, + }); + }, []); + return ( <> {!sloCalloutDismissed && ( @@ -113,6 +125,7 @@ export function TransactionOverview() { start={start} end={end} saveTableOptionsToUrl + onLoadTable={handleOnLoadTable} /> diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx index e4c4d592292b4..03ac43ff7b841 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx @@ -12,7 +12,6 @@ import { useChartThemes } from '@kbn/observability-shared-plugin/public'; import { uniqueId } from 'lodash'; import React, { useEffect, useMemo, useRef } from 'react'; import { i18n } from '@kbn/i18n'; -import { usePerformanceContext } from '@kbn/ebt-tools'; import type { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { isSuccess } from '../../../hooks/use_fetcher'; import { useFetcher, isPending } from '../../../hooks/use_fetcher'; @@ -29,10 +28,10 @@ export function CriticalPathFlamegraph( end: string; traceIds: string[]; traceIdsFetchStatus: FETCH_STATUS; + onLoadTable?: () => void; } & ({ serviceName: string; transactionName: string } | {}) ) { - const { start, end, traceIds, traceIdsFetchStatus } = props; - const { onPageReady } = usePerformanceContext(); + const { start, end, traceIds, traceIdsFetchStatus, onLoadTable } = props; const serviceName = 'serviceName' in props ? props.serviceName : null; const transactionName = 'transactionName' in props ? props.transactionName : null; @@ -68,18 +67,9 @@ export function CriticalPathFlamegraph( useEffect(() => { if (isSuccess(criticalPathFetchStatus) && isSuccess(traceIdsFetchStatus)) { - onPageReady({ - meta: { - rangeFrom: start, - rangeTo: end, - }, - customMetrics: { - key1: 'traceIds', - value1: traceIds.length, - }, - }); + onLoadTable?.(); } - }, [start, end, criticalPathFetchStatus, traceIdsFetchStatus, traceIds, onPageReady]); + }, [start, end, criticalPathFetchStatus, traceIdsFetchStatus, traceIds, onLoadTable]); const chartThemes = useChartThemes(); diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx index a562ceadc1aeb..a84c45f076bcb 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -8,7 +8,6 @@ import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { v4 as uuidv4 } from 'uuid'; -import { usePerformanceContext } from '@kbn/ebt-tools'; import { FormattedMessage } from '@kbn/i18n-react'; import { compact } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; @@ -21,7 +20,7 @@ import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; import { useApmRouter } from '../../../hooks/use_apm_router'; import { useBreakpoints } from '../../../hooks/use_breakpoints'; import { useStateDebounced } from '../../../hooks/use_debounce'; -import { FETCH_STATUS, isPending, useFetcher } from '../../../hooks/use_fetcher'; +import { FETCH_STATUS, isPending, isSuccess, useFetcher } from '../../../hooks/use_fetcher'; import { usePreferredDataSourceAndBucketSize } from '../../../hooks/use_preferred_data_source_and_bucket_size'; import type { APIReturnType } from '../../../services/rest/create_call_apm_api'; import { TransactionOverviewLink } from '../links/apm/transaction_overview_link'; @@ -93,7 +92,6 @@ export function TransactionsTable({ const { transactionType, serviceName } = useApmServiceContext(); const [searchQuery, setSearchQueryDebounced] = useStateDebounced(''); const [renderedItems, setRenderedItems] = useState([]); - const { onPageReady } = usePerformanceContext(); const { mainStatistics, mainStatisticsStatus, detailedStatistics, detailedStatisticsStatus } = useTableData({ @@ -111,22 +109,10 @@ export function TransactionsTable({ }); useEffect(() => { - if ( - mainStatisticsStatus === FETCH_STATUS.SUCCESS && - detailedStatisticsStatus === FETCH_STATUS.SUCCESS - ) { - if (onLoadTable) { - onLoadTable(); - } else { - onPageReady({ - meta: { - rangeFrom: start, - rangeTo: end, - }, - }); - } + if (isSuccess(mainStatisticsStatus) && isSuccess(detailedStatisticsStatus)) { + onLoadTable?.(); } - }, [mainStatisticsStatus, detailedStatisticsStatus, onLoadTable, end, start, onPageReady]); + }, [mainStatisticsStatus, detailedStatisticsStatus, onLoadTable, end, start]); const columns = useMemo(() => { return getColumns({ From 91fcf767ba0afd3807786d6689200272540d5219 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Fri, 14 Feb 2025 17:24:11 +0200 Subject: [PATCH 09/18] Update tests --- .../measure_interaction.test.tsx | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx index fb17d7ede35dd..bd69ecbfaaf51 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx @@ -22,14 +22,16 @@ describe('measureInteraction', () => { }); it('should mark the start of the page change', () => { - measureInteraction(); + const pathname = '/test-path'; + measureInteraction(pathname); expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.startPageChange); }); it('should mark the end of the page ready state and measure performance', () => { - const interaction = measureInteraction(); const pathname = '/test-path'; - interaction.pageReady(pathname); + const interaction = measureInteraction(pathname); + + interaction.pageReady(); expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); expect(performance.measure).toHaveBeenCalledWith(pathname, { @@ -43,14 +45,14 @@ describe('measureInteraction', () => { }); it('should include custom metrics and meta in the performance measure', () => { - const interaction = measureInteraction(); const pathname = '/test-path'; + const interaction = measureInteraction(pathname); const eventData = { customMetrics: { key1: 'foo-metric', value1: 100 }, meta: { rangeFrom: 'now-15m', rangeTo: 'now' }, }; - interaction.pageReady(pathname, eventData); + interaction.pageReady(eventData); expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); expect(performance.measure).toHaveBeenCalledWith(pathname, { @@ -70,15 +72,15 @@ describe('measureInteraction', () => { }); it('should handle absolute date format correctly', () => { - const interaction = measureInteraction(); const pathname = '/test-path'; + const interaction = measureInteraction(pathname); jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z const eventData = { meta: { rangeFrom: '2024-12-09T00:00:00Z', rangeTo: '2024-12-09T00:30:00Z' }, }; - interaction.pageReady(pathname, eventData); + interaction.pageReady(eventData); expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); expect(performance.measure).toHaveBeenCalledWith(pathname, { @@ -98,15 +100,15 @@ describe('measureInteraction', () => { }); it('should handle negative offset when rangeTo is in the past', () => { - const interaction = measureInteraction(); const pathname = '/test-path'; + const interaction = measureInteraction(pathname); jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z const eventData = { meta: { rangeFrom: '2024-12-08T00:00:00Z', rangeTo: '2024-12-09T00:00:00Z' }, }; - interaction.pageReady(pathname, eventData); + interaction.pageReady(eventData); expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); expect(performance.measure).toHaveBeenCalledWith(pathname, { @@ -126,15 +128,16 @@ describe('measureInteraction', () => { }); it('should handle positive offset when rangeTo is in the future', () => { - const interaction = measureInteraction(); const pathname = '/test-path'; + + const interaction = measureInteraction(pathname); jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z const eventData = { meta: { rangeFrom: '2024-12-08T01:00:00Z', rangeTo: '2024-12-09T01:00:00Z' }, }; - interaction.pageReady(pathname, eventData); + interaction.pageReady(eventData); expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); expect(performance.measure).toHaveBeenCalledWith(pathname, { @@ -154,16 +157,16 @@ describe('measureInteraction', () => { }); it('should set isInitialLoad to false on subsequent pageReady calls', () => { - const interaction = measureInteraction(); const pathname = '/test-path'; + const interaction = measureInteraction(pathname); jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z const eventData = { meta: { rangeFrom: '2024-12-08T01:00:00Z', rangeTo: '2024-12-09T01:00:00Z' }, }; - interaction.pageReady(pathname, eventData); - interaction.pageReady(pathname, eventData); + interaction.pageReady(eventData); + interaction.pageReady(eventData); expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); expect(performance.measure).toHaveBeenCalledWith(pathname, { From 23c098ef0f8366d3851bda27946238924d5db2b2 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Tue, 18 Feb 2025 13:24:57 +0200 Subject: [PATCH 10/18] Fix eslint --- .../trace_explorer_aggregated_critical_path.tsx | 2 +- .../aggregated_critical_path_tab.tsx | 2 +- .../components/app/transaction_overview/index.tsx | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx index dae5a7223dca6..d31b3443ac669 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx @@ -38,7 +38,7 @@ export function TraceExplorerAggregatedCriticalPath() { value1: traceIds.length, }, }); - }, []); + }, [start, end, traceIds, onPageReady]); return ( ; - } - const handleOnLoadTable = useCallback(() => { onPageReady({ meta: { @@ -80,7 +76,11 @@ export function TransactionOverview() { rangeTo: end, }, }); - }, []); + }, [start, end, onPageReady]); + + if (hasLogsOnlySignal) { + return ; + } return ( <> From da1b2c99be7140c24248cae26a02b977ac6b0b6a Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Tue, 18 Feb 2025 16:10:32 +0200 Subject: [PATCH 11/18] Rename markPerformanceRefreshStart to onPageRefreshStart --- .../context/measure_interaction/index.ts | 3 +++ .../src/performance_metrics/context/performance_context.tsx | 5 ++--- .../performance_metrics/context/use_performance_context.tsx | 4 ++-- .../public/components/shared/unified_search_bar/index.tsx | 4 ++-- .../hosts/components/search_bar/unified_search_bar.tsx | 6 +++--- .../metrics_explorer/hooks/use_metric_explorer_state.ts | 6 +++--- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts index ea856cdfce564..ccef11f828c9d 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts @@ -90,5 +90,8 @@ export function measureInteraction(pathname: string) { performance.clearMarks(perfomanceMarkers.endPageReady); } }, + pageRefreshStart() { + performance.mark(perfomanceMarkers.startPageRefresh); + }, }; } diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx index 4e563ee10b025..70c2c69c4af98 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx @@ -13,7 +13,6 @@ import { useLocation } from 'react-router-dom'; import { PerformanceApi, PerformanceContext } from './use_performance_context'; import { PerformanceMetricEvent } from '../../performance_metric_events'; import { measureInteraction } from './measure_interaction'; -import { perfomanceMarkers } from '../performance_markers'; export type CustomMetrics = Omit; @@ -49,8 +48,8 @@ export function PerformanceContextProvider({ children }: { children: React.React interaction.pageReady(eventData); } }, - markPerformanceRefreshStart() { - performance.mark(perfomanceMarkers.startPageRefresh); + onPageRefreshStart() { + interaction.pageRefreshStart(); }, }), [isRendered, interaction] diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx index bd664622c7c2d..68d724b7bf7cb 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx @@ -21,13 +21,13 @@ export interface PerformanceApi { * * Usage: * ```ts - * markPerformanceRefreshStart(); + * onPageRefreshStart(); * ``` * * The marker set by this function can later be used in performance measurements * along with an end marker end::pageReady to determine the total refresh duration. */ - markPerformanceRefreshStart(): void; + onPageRefreshStart(): void; } export const PerformanceContext = createContext(undefined); diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/index.tsx index c59de3f9d747e..fdf653be70e8c 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/index.tsx @@ -142,7 +142,7 @@ export function UnifiedSearchBar({ const { kuery, serviceName, environment, groupId, refreshPausedFromUrl, refreshIntervalFromUrl } = useSearchBarParams(value); - const { markPerformanceRefreshStart } = usePerformanceContext(); + const { onPageRefreshStart } = usePerformanceContext(); const timePickerTimeDefaults = core.uiSettings.get( UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS ); @@ -206,7 +206,7 @@ export function UnifiedSearchBar({ const onRefresh = () => { clearCache(); incrementTimeRangeId(); - markPerformanceRefreshStart(); + onPageRefreshStart(); }; const onRefreshChange = ({ isPaused, refreshInterval }: Partial) => { diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx index 63793e5cde1e2..d4d7596b4ef7f 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx @@ -25,7 +25,7 @@ export const UnifiedSearchBar = () => { const { metricsView } = useMetricsDataViewContext(); const { searchCriteria, onLimitChange, onPanelFiltersChange, onSubmit } = useUnifiedSearchContext(); - const { markPerformanceRefreshStart } = usePerformanceContext(); + const { onPageRefreshStart } = usePerformanceContext(); const { SearchBar } = unifiedSearch.ui; @@ -34,10 +34,10 @@ export const UnifiedSearchBar = () => { // This makes sure `onSubmit` is only called when the submit button is clicked if (isUpdate === false) { onSubmit(payload); - markPerformanceRefreshStart(); + onPageRefreshStart(); } }, - [onSubmit, markPerformanceRefreshStart] + [onSubmit, onPageRefreshStart] ); return ( diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts index c19f9e0ccdfc6..53653c218892e 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts @@ -36,7 +36,7 @@ export const useMetricsExplorerState = ({ enabled }: { enabled: boolean } = { en timestamps, setTimestamps, } = useMetricsExplorerOptionsContainerContext(); - const { markPerformanceRefreshStart } = usePerformanceContext(); + const { onPageRefreshStart } = usePerformanceContext(); const refreshTimestamps = useCallback(() => { const fromTimestamp = DateMath.parse(timeRange.from)!.valueOf(); @@ -47,8 +47,8 @@ export const useMetricsExplorerState = ({ enabled }: { enabled: boolean } = { en fromTimestamp, toTimestamp, }); - markPerformanceRefreshStart(); - }, [setTimestamps, timeRange, markPerformanceRefreshStart]); + onPageRefreshStart(); + }, [setTimestamps, timeRange, onPageRefreshStart]); const { data, error, fetchNextPage, isLoading } = useMetricsExplorerData({ options, From 4dd16aa9656d7f754a48947328a6f080de898707 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Tue, 18 Feb 2025 18:14:59 +0200 Subject: [PATCH 12/18] Fix tests --- .../measure_interaction.test.tsx | 321 ++++++++++-------- 1 file changed, 172 insertions(+), 149 deletions(-) diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx index bd69ecbfaaf51..9ca154ab01a86 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/measure_interaction.test.tsx @@ -15,173 +15,196 @@ describe('measureInteraction', () => { jest.restoreAllMocks(); }); - beforeEach(() => { - jest.clearAllMocks(); - performance.mark = jest.fn(); - performance.measure = jest.fn(); - }); - - it('should mark the start of the page change', () => { - const pathname = '/test-path'; - measureInteraction(pathname); - expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.startPageChange); - }); - - it('should mark the end of the page ready state and measure performance', () => { - const pathname = '/test-path'; - const interaction = measureInteraction(pathname); - - interaction.pageReady(); + describe('Initial load', () => { + beforeEach(() => { + jest.clearAllMocks(); + performance.mark = jest.fn(); + performance.measure = jest.fn(); + + performance.getEntriesByName = jest + .fn() + .mockReturnValueOnce([{ name: 'start::pageChange' }]) + .mockReturnValueOnce([{ name: 'end::pageReady' }]) + .mockReturnValueOnce([]); + performance.clearMarks = jest.fn(); + }); - expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); - expect(performance.measure).toHaveBeenCalledWith(pathname, { - detail: { - eventName: 'kibana:plugin_render_time', - type: 'kibana:performance', - }, - start: perfomanceMarkers.startPageChange, - end: perfomanceMarkers.endPageReady, + it('should mark the start of the page change', () => { + const pathname = '/test-path'; + measureInteraction(pathname); + expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.startPageChange); }); - }); - it('should include custom metrics and meta in the performance measure', () => { - const pathname = '/test-path'; - const interaction = measureInteraction(pathname); - const eventData = { - customMetrics: { key1: 'foo-metric', value1: 100 }, - meta: { rangeFrom: 'now-15m', rangeTo: 'now' }, - }; - - interaction.pageReady(eventData); - - expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); - expect(performance.measure).toHaveBeenCalledWith(pathname, { - detail: { - eventName: 'kibana:plugin_render_time', - type: 'kibana:performance', - customMetrics: eventData.customMetrics, - meta: { - queryRangeSecs: 900, - queryOffsetSecs: 0, - isInitialLoad: true, + it('should mark the end of the page ready state and measure performance', () => { + const pathname = '/test-path'; + const interaction = measureInteraction(pathname); + + interaction.pageReady(); + + expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); + expect(performance.measure).toHaveBeenCalledWith(`[ttfmp:initial] - ${pathname}`, { + detail: { + customMetrics: undefined, + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + meta: { + isInitialLoad: true, + }, }, - }, - end: 'end::pageReady', - start: 'start::pageChange', + start: perfomanceMarkers.startPageChange, + end: perfomanceMarkers.endPageReady, + }); }); - }); - it('should handle absolute date format correctly', () => { - const pathname = '/test-path'; - const interaction = measureInteraction(pathname); - jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z - - const eventData = { - meta: { rangeFrom: '2024-12-09T00:00:00Z', rangeTo: '2024-12-09T00:30:00Z' }, - }; - - interaction.pageReady(eventData); - - expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); - expect(performance.measure).toHaveBeenCalledWith(pathname, { - detail: { - eventName: 'kibana:plugin_render_time', - type: 'kibana:performance', - customMetrics: undefined, - meta: { - queryRangeSecs: 1800, - queryOffsetSecs: 0, - isInitialLoad: true, + it('should include custom metrics and meta in the performance measure', () => { + const pathname = '/test-path'; + const interaction = measureInteraction(pathname); + const eventData = { + customMetrics: { key1: 'foo-metric', value1: 100 }, + meta: { rangeFrom: 'now-15m', rangeTo: 'now' }, + }; + + interaction.pageReady(eventData); + + expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); + expect(performance.measure).toHaveBeenCalledWith(`[ttfmp:initial] - ${pathname}`, { + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: eventData.customMetrics, + meta: { + queryRangeSecs: 900, + queryOffsetSecs: 0, + isInitialLoad: true, + }, }, - }, - end: 'end::pageReady', - start: 'start::pageChange', + end: 'end::pageReady', + start: 'start::pageChange', + }); }); - }); - it('should handle negative offset when rangeTo is in the past', () => { - const pathname = '/test-path'; - const interaction = measureInteraction(pathname); - jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z - - const eventData = { - meta: { rangeFrom: '2024-12-08T00:00:00Z', rangeTo: '2024-12-09T00:00:00Z' }, - }; - - interaction.pageReady(eventData); - - expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); - expect(performance.measure).toHaveBeenCalledWith(pathname, { - detail: { - eventName: 'kibana:plugin_render_time', - type: 'kibana:performance', - customMetrics: undefined, - meta: { - queryRangeSecs: 86400, - queryOffsetSecs: -1800, - isInitialLoad: true, + it('should handle absolute date format correctly', () => { + const pathname = '/test-path'; + const interaction = measureInteraction(pathname); + jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z + + const eventData = { + meta: { rangeFrom: '2024-12-09T00:00:00Z', rangeTo: '2024-12-09T00:30:00Z' }, + }; + + interaction.pageReady(eventData); + + expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); + expect(performance.measure).toHaveBeenCalledWith(`[ttfmp:initial] - ${pathname}`, { + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: undefined, + meta: { + queryRangeSecs: 1800, + queryOffsetSecs: 0, + isInitialLoad: true, + }, }, - }, - end: 'end::pageReady', - start: 'start::pageChange', + end: 'end::pageReady', + start: 'start::pageChange', + }); }); - }); - - it('should handle positive offset when rangeTo is in the future', () => { - const pathname = '/test-path'; - - const interaction = measureInteraction(pathname); - jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z - const eventData = { - meta: { rangeFrom: '2024-12-08T01:00:00Z', rangeTo: '2024-12-09T01:00:00Z' }, - }; - - interaction.pageReady(eventData); + it('should handle negative offset when rangeTo is in the past', () => { + const pathname = '/test-path'; + const interaction = measureInteraction(pathname); + jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z + + const eventData = { + meta: { rangeFrom: '2024-12-08T00:00:00Z', rangeTo: '2024-12-09T00:00:00Z' }, + }; + + interaction.pageReady(eventData); + + expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); + expect(performance.measure).toHaveBeenCalledWith(`[ttfmp:initial] - ${pathname}`, { + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: undefined, + meta: { + queryRangeSecs: 86400, + queryOffsetSecs: -1800, + isInitialLoad: true, + }, + }, + end: 'end::pageReady', + start: 'start::pageChange', + }); + }); - expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); - expect(performance.measure).toHaveBeenCalledWith(pathname, { - detail: { - eventName: 'kibana:plugin_render_time', - type: 'kibana:performance', - customMetrics: undefined, - meta: { - queryRangeSecs: 86400, - queryOffsetSecs: 1800, - isInitialLoad: true, + it('should handle positive offset when rangeTo is in the future', () => { + const pathname = '/test-path'; + + const interaction = measureInteraction(pathname); + jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z + + const eventData = { + meta: { rangeFrom: '2024-12-08T01:00:00Z', rangeTo: '2024-12-09T01:00:00Z' }, + }; + + interaction.pageReady(eventData); + + expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); + expect(performance.measure).toHaveBeenCalledWith(`[ttfmp:initial] - ${pathname}`, { + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: undefined, + meta: { + queryRangeSecs: 86400, + queryOffsetSecs: 1800, + isInitialLoad: true, + }, }, - }, - end: 'end::pageReady', - start: 'start::pageChange', + end: 'end::pageReady', + start: 'start::pageChange', + }); + expect(performance.clearMarks).toHaveBeenCalledWith(perfomanceMarkers.startPageChange); + expect(performance.clearMarks).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); }); }); - it('should set isInitialLoad to false on subsequent pageReady calls', () => { - const pathname = '/test-path'; - const interaction = measureInteraction(pathname); - jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z - - const eventData = { - meta: { rangeFrom: '2024-12-08T01:00:00Z', rangeTo: '2024-12-09T01:00:00Z' }, - }; - - interaction.pageReady(eventData); - interaction.pageReady(eventData); - - expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); - expect(performance.measure).toHaveBeenCalledWith(pathname, { - detail: { - eventName: 'kibana:plugin_render_time', - type: 'kibana:performance', - customMetrics: undefined, - meta: { - queryRangeSecs: 86400, - queryOffsetSecs: 1800, - isInitialLoad: false, + describe('Refresh', () => { + beforeEach(() => { + performance.getEntriesByName = jest + .fn() + .mockReturnValue([{ name: 'start::pageRefresh' }]) + .mockReturnValue([{ name: 'end::pageReady' }]); + }); + it('should set isInitialLoad to false on refresh calls', () => { + const pathname = '/test-path'; + const interaction = measureInteraction(pathname); + + jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z + + const eventData = { + meta: { rangeFrom: '2024-12-08T01:00:00Z', rangeTo: '2024-12-09T01:00:00Z' }, + }; + + interaction.pageReady(eventData); + + expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady); + expect(performance.measure).toHaveBeenCalledWith(`[ttfmp:refresh] - ${pathname}`, { + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: undefined, + meta: { + queryRangeSecs: 86400, + queryOffsetSecs: 1800, + isInitialLoad: false, + }, }, - }, - end: 'end::pageReady', - start: 'start::pageChange', + end: 'end::pageReady', + start: 'start::pageRefresh', + }); }); }); }); From 294a3e52c96f480ff017daff7b779191da539d78 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Tue, 18 Feb 2025 18:18:34 +0200 Subject: [PATCH 13/18] Fix onRefresh for metrics explorer --- .../pages/metrics/metrics_explorer/index.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx index 4ddc1d55498c8..ea66ebdbb86fc 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx @@ -96,14 +96,16 @@ const MetricsExplorerContent = () => { currentTimerange: timeRange, }; - if (!isLoading) { - onPageReady({ - meta: { - rangeFrom: timeRange.from, - rangeTo: timeRange.to, - }, - }); - } + useEffect(() => { + if (!isLoading && data) { + onPageReady({ + meta: { + rangeFrom: timeRange.from, + rangeTo: timeRange.to, + }, + }); + } + }, [isLoading, data]); return ( Date: Wed, 19 Feb 2025 15:33:55 +0200 Subject: [PATCH 14/18] Fix lint --- .../infra/public/pages/metrics/metrics_explorer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx index ea66ebdbb86fc..9d3469fa1b841 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx @@ -105,7 +105,7 @@ const MetricsExplorerContent = () => { }, }); } - }, [isLoading, data]); + }, [isLoading, data, timeRange.from, timeRange.to, onPageReady]); return ( Date: Tue, 25 Feb 2025 12:42:41 +0200 Subject: [PATCH 15/18] Fix apm tests --- .../shared/search_bar/search_bar.test.tsx | 23 +++++++++++-------- .../unified_search_bar.test.tsx | 23 ++++++++++++------- .../apm_plugin/mock_apm_plugin_context.tsx | 2 ++ 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx index 04f1b4f39352f..6dd4063e64452 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import { getByTestId, fireEvent, getByText, act } from '@testing-library/react'; +import { getByTestId, fireEvent, getByText, act, waitFor } from '@testing-library/react'; import type { MemoryHistory } from 'history'; +import { PerformanceContextProvider } from '@kbn/ebt-tools'; import { createMemoryHistory } from 'history'; import React from 'react'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; @@ -79,7 +80,9 @@ function setup({ - + + + @@ -96,7 +99,7 @@ describe('when transactionType is selected and multiple transaction types are gi jest.spyOn(history, 'replace'); }); - it('renders a radio group with transaction types', () => { + it('renders a radio group with transaction types', async () => { const { container } = setup({ history, serviceTransactionTypes: ['firstType', 'secondType'], @@ -108,14 +111,16 @@ describe('when transactionType is selected and multiple transaction types are gi }); // transaction type selector - const dropdown = getByTestId(container, 'headerFilterTransactionType'); + await waitFor(() => { + const dropdown = getByTestId(container, 'headerFilterTransactionType'); - // both options should be listed - expect(getByText(dropdown, 'firstType')).toBeInTheDocument(); - expect(getByText(dropdown, 'secondType')).toBeInTheDocument(); + // both options should be listed + expect(getByText(dropdown, 'firstType')).toBeInTheDocument(); + expect(getByText(dropdown, 'secondType')).toBeInTheDocument(); - // second option should be selected - expect(dropdown).toHaveValue('secondType'); + // second option should be selected + expect(dropdown).toHaveValue('secondType'); + }); }); it('should update the URL when a transaction type is selected', async () => { diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx index 7600cc6c0e17f..45864875b526b 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx @@ -6,6 +6,8 @@ */ import { mount } from 'enzyme'; +import { waitFor } from '@testing-library/react'; +import { PerformanceContextProvider } from '@kbn/ebt-tools'; import type { MemoryHistory } from 'history'; import { createMemoryHistory } from 'history'; import React from 'react'; @@ -25,7 +27,7 @@ jest.mock('react-router-dom', () => ({ useLocation: jest.fn(), })); -function setup({ urlParams, history }: { urlParams: UrlParams; history: MemoryHistory }) { +async function setup({ urlParams, history }: { urlParams: UrlParams; history: MemoryHistory }) { history.replace({ pathname: '/services', search: fromQuery(urlParams), @@ -73,7 +75,9 @@ function setup({ urlParams, history }: { urlParams: UrlParams; history: MemoryHi } as unknown as ApmPluginContextValue } > - + + + ); @@ -89,6 +93,7 @@ function setup({ urlParams, history }: { urlParams: UrlParams; history: MemoryHi describe('when kuery is already present in the url, the search bar must reflect the same', () => { let history: MemoryHistory; + beforeEach(() => { history = createMemoryHistory(); jest.spyOn(history, 'push'); @@ -103,12 +108,12 @@ describe('when kuery is already present in the url, the search bar must reflect const search = '?method=json'; const pathname = '/services'; - (useLocation as jest.Mock).mockImplementationOnce(() => ({ + (useLocation as jest.Mock).mockReturnValue(() => ({ search, pathname, })); - it('sets the searchbar value based on URL', () => { + it('sets the searchbar value based on URL', async () => { const expectedQuery = { query: 'service.name:"opbeans-android"', language: 'kuery', @@ -137,13 +142,15 @@ describe('when kuery is already present in the url, the search bar must reflect }; jest.spyOn(useApmParamsHook, 'useApmParams').mockReturnValue({ query: urlParams, path: {} }); - const { setQuerySpy, setTimeSpy, setRefreshIntervalSpy } = setup({ + const { setQuerySpy, setTimeSpy, setRefreshIntervalSpy } = await setup({ history, urlParams, }); - expect(setQuerySpy).toBeCalledWith(expectedQuery); - expect(setTimeSpy).toBeCalledWith(expectedTimeRange); - expect(setRefreshIntervalSpy).toBeCalledWith(refreshInterval); + await waitFor(() => { + expect(setQuerySpy).toBeCalledWith(expectedQuery); + expect(setTimeSpy).toBeCalledWith(expectedTimeRange); + expect(setRefreshIntervalSpy).toBeCalledWith(refreshInterval); + }); }); }); diff --git a/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index 10c5643c7ac1e..e09a83e9165c4 100644 --- a/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -208,6 +208,8 @@ export function MockApmPluginContextWrapper({ createCallApmApi(contextValue.core); } + performance.mark = jest.fn(); + const contextHistory = useHistory(); const usedHistory = useMemo(() => { From 466045c839ebac4b46974c8f22e1f4c55b459554 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Tue, 25 Feb 2025 13:18:53 +0200 Subject: [PATCH 16/18] Fix tests --- .../src/track_performance_measure_entries.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.test.ts b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.test.ts index ab4de0e45fc99..54346abe3fe3d 100644 --- a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.test.ts +++ b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.test.ts @@ -114,6 +114,11 @@ describe('trackPerformanceMeasureEntries', () => { anyKey: 'anyKey', anyValue: 'anyValue', }, + meta: { + isInitialLoad: true, + queryRangeSecs: 86400, + queryOffsetSecs: 0, + }, }, }, ]); @@ -124,7 +129,7 @@ describe('trackPerformanceMeasureEntries', () => { duration: 1000, eventName: 'kibana:plugin_render_time', key1: 'key1', - meta: { target: '/' }, + meta: { is_initial_load: true, query_range_secs: 86400, query_offset_secs: 0 }, value1: 'value1', }); }); @@ -152,7 +157,7 @@ describe('trackPerformanceMeasureEntries', () => { expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('performance_metric', { duration: 1000, eventName: 'kibana:plugin_render_time', - meta: { target: '/', query_range_secs: 86400, query_offset_secs: 0 }, + meta: { query_range_secs: 86400, query_offset_secs: 0 }, }); }); }); From c9be5094ab419370c9e142b0713e652909846226 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Tue, 25 Feb 2025 13:33:51 +0200 Subject: [PATCH 17/18] trigger onPageReady only when data changes --- .../infra/public/pages/metrics/metrics_explorer/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx index 9d3469fa1b841..de29813a08b26 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { useTrackPageview, FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public'; import { usePerformanceContext } from '@kbn/ebt-tools'; import { OnboardingFlow } from '../../../components/shared/templates/no_data_config'; @@ -65,7 +65,7 @@ const MetricsExplorerContent = () => { const { currentView } = useMetricsExplorerViews(); const { kibanaVersion, isCloudEnv, isServerlessEnv } = useKibanaEnvironmentContext(); - + const prevDataRef = useRef(data); const { onPageReady } = usePerformanceContext(); useTrackPageview({ app: 'infra_metrics', path: 'metrics_explorer' }); @@ -97,13 +97,15 @@ const MetricsExplorerContent = () => { }; useEffect(() => { - if (!isLoading && data) { + if (!isLoading && data && prevDataRef.current !== data) { onPageReady({ meta: { rangeFrom: timeRange.from, rangeTo: timeRange.to, }, }); + + prevDataRef.current = data; } }, [isLoading, data, timeRange.from, timeRange.to, onPageReady]); From 3a95be3200355ba6ce3ac0591e80039574c3684e Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Tue, 25 Feb 2025 13:33:59 +0200 Subject: [PATCH 18/18] Fix infra tests --- .../hooks/use_metric_explorer_state.test.tsx | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.test.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.test.tsx index 0e70071f199ff..48f1c786737d6 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.test.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.test.tsx @@ -5,7 +5,9 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react'; +import { renderHook, act, waitFor } from '@testing-library/react'; +import { PerformanceContextProvider } from '@kbn/ebt-tools'; +import { useLocation } from 'react-router-dom'; import { useMetricsExplorerState } from './use_metric_explorer_state'; import { MetricsExplorerOptionsContainer } from './use_metrics_explorer_options'; import React from 'react'; @@ -16,6 +18,11 @@ jest.mock('../../../../hooks/use_kibana_timefilter_time', () => ({ useSyncKibanaTimeFilterTime: () => [() => {}], })); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(), +})); + jest.mock('../../../../alerting/use_alert_prefill', () => ({ useAlertPrefillContext: () => ({ metricThresholdPrefill: { @@ -27,7 +34,9 @@ jest.mock('../../../../alerting/use_alert_prefill', () => ({ const renderUseMetricsExplorerStateHook = () => renderHook(() => useMetricsExplorerState(), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - {children} + + {children} + ), }); @@ -73,6 +82,12 @@ describe('useMetricsExplorerState', () => { }); delete STORE.MetricsExplorerOptions; delete STORE.MetricsExplorerTimeRange; + + const pathname = '/hosts'; + (useLocation as jest.Mock).mockReturnValue(() => ({ + pathname, + })); + performance.mark = jest.fn(); }); afterEach(() => { @@ -88,9 +103,11 @@ describe('useMetricsExplorerState', () => { }, }); const { result } = renderUseMetricsExplorerStateHook(); - expect(result.current.data!.pages[0]).toEqual(resp); - expect(result.current.error).toBe(null); - expect(result.current.isLoading).toBe(false); + await waitFor(() => { + expect(result.current.data!.pages[0]).toEqual(resp); + expect(result.current.error).toBe(null); + expect(result.current.isLoading).toBe(false); + }); }); describe('handleRefresh', () => {