diff --git a/src/platform/plugins/shared/discover_shared/public/services/discover_features/types.ts b/src/platform/plugins/shared/discover_shared/public/services/discover_features/types.ts
index 593fe0970ffc9..583116f6472b4 100644
--- a/src/platform/plugins/shared/discover_shared/public/services/discover_features/types.ts
+++ b/src/platform/plugins/shared/discover_shared/public/services/discover_features/types.ts
@@ -16,9 +16,10 @@ import type {
ErrorsByTraceId,
TraceRootSpan,
UnifiedSpanDocument,
+ FocusedTraceWaterfallProps,
+ FullTraceWaterfallProps,
} from '@kbn/apm-types';
-import type { ProcessorEvent } from '@kbn/apm-types-shared';
-import type { HistogramItem } from '@kbn/apm-types-shared';
+import type { HistogramItem, ProcessorEvent } from '@kbn/apm-types-shared';
import type { DataView } from '@kbn/data-views-plugin/common';
import type React from 'react';
import type { IndicatorType } from '@kbn/slo-schema';
@@ -126,6 +127,16 @@ export type SecuritySolutionFeature =
/** **************** Observability Traces ****************/
+interface ObservabilityFocusedTraceWaterfallFeature {
+ id: 'observability-focused-trace-waterfall';
+ render: (props: FocusedTraceWaterfallProps) => JSX.Element;
+}
+
+interface ObservabilityFullTraceWaterfallFeature {
+ id: 'observability-full-trace-waterfall';
+ render: (props: FullTraceWaterfallProps) => JSX.Element;
+}
+
export interface ObservabilityTracesSpanLinksFeature {
id: 'observability-traces-fetch-span-links';
fetchSpanLinks: (
@@ -224,7 +235,9 @@ export type ObservabilityTracesFeature =
| ObservabilityTracesFetchRootSpanByTraceIdFeature
| ObservabilityTracesFetchSpanFeature
| ObservabilityTracesFetchLatencyOverallTransactionDistributionFeature
- | ObservabilityTracesFetchLatencyOverallSpanDistributionFeature;
+ | ObservabilityTracesFetchLatencyOverallSpanDistributionFeature
+ | ObservabilityFocusedTraceWaterfallFeature
+ | ObservabilityFullTraceWaterfallFeature;
/** ****************************************************************************************/
diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/index.test.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/index.test.tsx
index 00f9daa373de7..4f2903ea2061c 100644
--- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/index.test.tsx
+++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/index.test.tsx
@@ -7,33 +7,12 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import React from 'react';
-import { render, screen, act, waitFor } from '@testing-library/react';
-import {
- FullScreenWaterfall,
- type FullScreenWaterfallProps,
- EUI_FLYOUT_BODY_OVERFLOW_CLASS,
-} from '.';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
-
-let capturedCallbacks: any = null;
-
-jest.mock('@kbn/embeddable-plugin/public', () => ({
- EmbeddableRenderer: ({ type, getParentApi, hidePanelChrome }: any) => {
- const api = getParentApi();
- capturedCallbacks = api.getSerializedStateForChild();
-
- return (
-
- Embeddable Renderer Mock
-
- );
- },
-}));
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+import { FullScreenWaterfall, type FullScreenWaterfallProps } from '.';
+import { setUnifiedDocViewerServices } from '../../../../../plugin';
+import type { UnifiedDocViewerServices } from '../../../../../types';
jest.mock('./waterfall_flyout/span_flyout', () => ({
SpanFlyout: ({ traceId, spanId, _, activeSection }: any) => (
@@ -62,30 +41,22 @@ describe('FullScreenWaterfall', () => {
onExitFullScreen: jest.fn(),
};
- beforeEach(() => {
- jest.clearAllMocks();
- capturedCallbacks = null;
+ beforeAll(() => {
+ setUnifiedDocViewerServices({
+ discoverShared: {
+ features: {
+ registry: {
+ getById: () => ({
+ render: () => FullTraceWaterfall
,
+ }),
+ },
+ },
+ },
+ } as unknown as UnifiedDocViewerServices);
});
- it('should render APM trace waterfall embeddable with hidden chrome', () => {
- render();
-
- const embeddable = screen.getByTestId('embeddableRenderer');
- expect(embeddable).toHaveAttribute('data-type', 'APM_TRACE_WATERFALL_EMBEDDABLE');
- expect(embeddable).toHaveAttribute('data-hide-panel-chrome', 'true');
- });
-
- it('wraps EmbeddableRenderer with CSS override for proper layout', () => {
- const { container } = render();
-
- const embeddable = container.querySelector('[data-test-subj="embeddableRenderer"]');
- expect(embeddable).toBeInTheDocument();
-
- const wrapper = embeddable?.parentElement;
- expect(wrapper).toHaveStyleRule('width', '100%');
- expect(wrapper).toHaveStyleRule('display', 'block!important', {
- target: '.embPanel__content',
- });
+ beforeEach(() => {
+ jest.clearAllMocks();
});
it('should not display nested flyouts initially', () => {
@@ -95,82 +66,17 @@ describe('FullScreenWaterfall', () => {
expect(screen.queryByTestId('logsFlyout')).not.toBeInTheDocument();
});
- describe('nested flyout interactions', () => {
- it('should display span details when clicking a waterfall node', () => {
- render();
-
- act(() => {
- capturedCallbacks.onNodeClick('test-span-id');
- });
-
- const spanFlyout = screen.getByTestId('spanFlyout');
- expect(spanFlyout).toHaveAttribute('data-trace-id', 'test-trace-id');
- expect(spanFlyout).toHaveAttribute('data-span-id', 'test-span-id');
- expect(spanFlyout).not.toHaveAttribute('data-active-section');
- expect(screen.queryByTestId('logsFlyout')).not.toBeInTheDocument();
- });
-
- it('should display span errors table when clicking an error with multiple occurrences', () => {
- render();
-
- act(() => {
- capturedCallbacks.onErrorClick({
- traceId: 'test-trace-id',
- docId: 'test-error-doc-id',
- errorCount: 5,
- });
- });
-
- const spanFlyout = screen.getByTestId('spanFlyout');
- expect(spanFlyout).toHaveAttribute('data-active-section', 'errors-table');
- expect(screen.queryByTestId('logsFlyout')).not.toBeInTheDocument();
- });
-
- it('should display log details when clicking a single error', () => {
- render();
-
- act(() => {
- capturedCallbacks.onErrorClick({
- traceId: 'test-trace-id',
- docId: 'test-doc-id',
- errorCount: 1,
- errorDocId: 'test-error-log-id',
- });
- });
-
- expect(screen.getByTestId('logsFlyout')).toHaveAttribute('data-id', 'test-error-log-id');
- expect(screen.queryByTestId('spanFlyout')).not.toBeInTheDocument();
- });
-
- it('should not open any flyout when clicking a single error without errorDocId', () => {
- render();
-
- act(() => {
- capturedCallbacks.onErrorClick({
- traceId: 'test-trace-id',
- docId: 'test-doc-id',
- errorCount: 1,
- });
- });
+ it('should display the full trace waterfall', () => {
+ render();
- expect(screen.queryByTestId('spanFlyout')).not.toBeInTheDocument();
- expect(screen.queryByTestId('logsFlyout')).not.toBeInTheDocument();
- });
+ expect(screen.getByTestId('fullTraceWaterfall')).toBeInTheDocument();
});
- describe('scrollElement integration', () => {
- it('should pass scrollElement with correct EUI class to embeddable', async () => {
- render();
-
- await waitFor(() => {
- expect(screen.getByTestId('embeddableRenderer')).toBeInTheDocument();
- });
+ describe('when service name is undefined', () => {
+ it('does not display the full trace waterfall', () => {
+ render();
- expect(capturedCallbacks.scrollElement).not.toBeNull();
- expect(capturedCallbacks.scrollElement).toBeInstanceOf(Element);
- expect(
- capturedCallbacks.scrollElement.classList.contains(EUI_FLYOUT_BODY_OVERFLOW_CLASS)
- ).toBe(true);
+ expect(screen.queryByTestId('fullTraceWaterfall')).not.toBeInTheDocument();
});
});
});
diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/index.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/index.tsx
index 389ce18c57cca..e032d163eb542 100644
--- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/index.tsx
+++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/full_screen_waterfall/index.tsx
@@ -12,19 +12,18 @@ import {
EuiFlyoutBody,
EuiFlyoutHeader,
EuiTitle,
- useGeneratedHtmlId,
useEuiTheme,
+ useGeneratedHtmlId,
} from '@elastic/eui';
-import { css } from '@emotion/react';
-import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/types';
import React, { useCallback, useState } from 'react';
+import { getUnifiedDocViewerServices } from '../../../../../plugin';
import type { TraceOverviewSections } from '../../doc_viewer_overview/overview';
-import type { spanFlyoutId as spanFlyoutIdType } from './waterfall_flyout/span_flyout';
-import { SpanFlyout, spanFlyoutId } from './waterfall_flyout/span_flyout';
import type { logsFlyoutId as logsFlyoutIdType } from './waterfall_flyout/logs_flyout';
import { LogsFlyout, logsFlyoutId } from './waterfall_flyout/logs_flyout';
+import type { spanFlyoutId as spanFlyoutIdType } from './waterfall_flyout/span_flyout';
+import { SpanFlyout, spanFlyoutId } from './waterfall_flyout/span_flyout';
export const EUI_FLYOUT_BODY_OVERFLOW_CLASS = 'euiFlyoutBody__overflow';
@@ -45,6 +44,10 @@ export const FullScreenWaterfall = ({
serviceName,
onExitFullScreen,
}: FullScreenWaterfallProps) => {
+ const { discoverShared } = getUnifiedDocViewerServices();
+ const FullTraceWaterfall = discoverShared.features.registry.getById(
+ 'observability-full-trace-waterfall'
+ )?.render;
const { euiTheme } = useEuiTheme();
const [docId, setDocId] = useState(null);
const [docIndex, setDocIndex] = useState(undefined);
@@ -71,7 +74,6 @@ export const FullScreenWaterfall = ({
* Obtains the EUI flyout scroll container for the trace waterfall embeddable.
*
* This pattern is necessary because:
- * - Embeddables are constructed once with immutable initial state
* - EUI components don't expose refs, requiring a wrapper div with closest()
* - scrollElement must be available before the embeddable initializes (conditional render below)
*
@@ -79,49 +81,12 @@ export const FullScreenWaterfall = ({
* TODO: Once the EUI team implements a scrollRef prop (or exposes refs on EUIFlyoutBody, Issue: 2564 in kibana-team repository),
* we can replace this workaround with a direct ref usage.
*/
- const embeddableContainerRef = useCallback((node: HTMLDivElement | null) => {
+ const waterfallContainerRef = useCallback((node: HTMLDivElement | null) => {
if (node) {
setScrollElement(node.closest(`.${EUI_FLYOUT_BODY_OVERFLOW_CLASS}`) ?? null);
}
}, []);
- const getParentApi = useCallback(() => {
- return {
- getSerializedStateForChild: () => ({
- traceId,
- rangeFrom,
- rangeTo,
- serviceName,
- scrollElement,
- onErrorClick: (params: {
- traceId: string;
- docId: string;
- errorCount: number;
- errorDocId?: string;
- docIndex?: string;
- }) => {
- if (params.errorCount > 1) {
- setActiveFlyoutId(spanFlyoutId);
- setActiveSection('errors-table');
- setDocId(params.docId);
- setDocIndex(undefined);
- } else if (params.errorDocId) {
- setActiveFlyoutId(logsFlyoutId);
- setDocId(params.errorDocId);
- setDocIndex(params.docIndex);
- }
- },
- onNodeClick: (nodeSpanId: string) => {
- setActiveSection(undefined);
- setDocId(nodeSpanId);
- setDocIndex(undefined);
- setActiveFlyoutId(spanFlyoutId);
- },
- mode: 'full',
- }),
- };
- }, [traceId, rangeFrom, rangeTo, serviceName, scrollElement]);
-
function handleCloseFlyout() {
setActiveFlyoutId(null);
setActiveSection(undefined);
@@ -129,6 +94,36 @@ export const FullScreenWaterfall = ({
setDocIndex(undefined);
}
+ function handleNodeClick(nodeSpanId: string) {
+ setActiveSection(undefined);
+ setDocId(nodeSpanId);
+ setDocIndex(undefined);
+ setActiveFlyoutId(spanFlyoutId);
+ }
+
+ function handleErrorClick(params: {
+ traceId: string;
+ docId: string;
+ errorCount: number;
+ errorDocId?: string;
+ docIndex?: string;
+ }) {
+ if (params.errorCount > 1) {
+ setActiveFlyoutId(spanFlyoutId);
+ setActiveSection('errors-table');
+ setDocId(params.docId);
+ setDocIndex(undefined);
+ } else if (params.errorDocId) {
+ setActiveFlyoutId(logsFlyoutId);
+ setDocId(params.errorDocId);
+ setDocIndex(params.docIndex);
+ }
+ }
+
+ if (!FullTraceWaterfall) {
+ return null;
+ }
+
return (
- {scrollElement ? (
-
+ {scrollElement && serviceName ? (
+
) : null}
diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace_waterfall/index.test.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace_waterfall/index.test.tsx
deleted file mode 100644
index fc26d1e28bd68..0000000000000
--- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace_waterfall/index.test.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the "Elastic License
- * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
- * Public License v 1"; you may not use this file except in compliance with, at
- * your election, the "Elastic License 2.0", the "GNU Affero General Public
- * License v3.0 only", or the "Server Side Public License, v 1".
- */
-
-import React from 'react';
-import { render } from '@testing-library/react';
-import { TraceWaterfall } from '.';
-import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub';
-
-jest.mock('../../../../../plugin', () => ({
- getUnifiedDocViewerServices: () => ({
- data: {
- query: {
- timefilter: {
- timefilter: {
- getAbsoluteTime: () => ({ from: '2024-01-01', to: '2024-01-02' }),
- },
- },
- },
- },
- }),
-}));
-
-jest.mock('@kbn/embeddable-plugin/public', () => ({
- EmbeddableRenderer: () => ,
-}));
-
-jest.mock('../full_screen_waterfall', () => ({
- FullScreenWaterfall: () => null,
-}));
-
-jest.mock('./full_screen_waterfall_tour_step', () => ({
- TraceWaterfallTourStep: () => null,
-}));
-
-jest.mock('../../../../..', () => ({
- ContentFrameworkSection: ({ children }: { children: React.ReactNode }) => {children}
,
-}));
-
-describe('TraceWaterfall', () => {
- const dataView = createStubDataView({
- spec: {
- id: 'test-dataview',
- title: 'test-pattern',
- timeFieldName: '@timestamp',
- },
- });
-
- it('wraps EmbeddableRenderer with CSS override for proper layout', () => {
- const { container } = render();
-
- const embeddable = container.querySelector('[data-test-subj="embeddable-renderer"]');
- expect(embeddable).toBeInTheDocument();
-
- // Verify the wrapper exists with the CSS override
- const wrapper = embeddable?.parentElement;
- expect(wrapper).toHaveStyleRule('width', '100%');
- expect(wrapper).toHaveStyleRule('display', 'block!important', {
- target: '.embPanel__content',
- });
- });
-});
diff --git a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace_waterfall/index.tsx b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace_waterfall/index.tsx
index 08aededd90cca..3d090f41007b1 100644
--- a/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace_waterfall/index.tsx
+++ b/src/platform/plugins/shared/unified_doc_viewer/public/components/observability/traces/components/trace_waterfall/index.tsx
@@ -7,12 +7,10 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public';
+import { EuiDelayRender } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/types';
-import React, { useCallback, useState } from 'react';
-import { EuiDelayRender } from '@elastic/eui';
-import { css } from '@emotion/react';
+import React, { useState } from 'react';
import { ContentFrameworkSection } from '../../../../..';
import { getUnifiedDocViewerServices } from '../../../../../plugin';
import { FullScreenWaterfall } from '../full_screen_waterfall';
@@ -39,22 +37,16 @@ const sectionTitle = i18n.translate('unifiedDocViewer.observability.traces.trace
});
export function TraceWaterfall({ traceId, docId, serviceName, dataView }: Props) {
- const { data } = getUnifiedDocViewerServices();
+ const { data, discoverShared } = getUnifiedDocViewerServices();
+ const FocusedTraceWaterfall = discoverShared.features.registry.getById(
+ 'observability-focused-trace-waterfall'
+ )?.render;
const [showFullScreenWaterfall, setShowFullScreenWaterfall] = useState(false);
const { from: rangeFrom, to: rangeTo } = data.query.timefilter.timefilter.getAbsoluteTime();
- const getParentApi = useCallback(
- () => ({
- getSerializedStateForChild: () => ({
- traceId,
- rangeFrom,
- rangeTo,
- docId,
- mode: 'summary',
- }),
- }),
- [docId, rangeFrom, rangeTo, traceId]
- );
+ if (!FocusedTraceWaterfall) {
+ return null;
+ }
const actionId = 'traceWaterfallFullScreenAction';
return (
@@ -86,25 +78,14 @@ export function TraceWaterfall({ traceId, docId, serviceName, dataView }: Props)
},
]}
>
- {/* TODO: This is a workaround for layout issues when using hidePanelChrome outside of Dashboard.
- The PresentationPanel applies flex styles (.embPanel__content) that cause width: 0 in non-Dashboard contexts.
- This should be removed once PresentationPanel properly supports hidePanelChrome as an out-of-the-box solution.
- Issue: https://github.com/elastic/kibana/issues/248307
- */}
-
-
-
+ ) : null}
void;
+ onErrorClick?: FullTraceWaterfallOnErrorClick;
+}
+
+export type FullTraceWaterfallOnErrorClick = (params: {
+ traceId: string;
+ docId: string;
+ errorCount: number;
+ errorDocId?: string;
+ docIndex?: string;
+}) => void;
diff --git a/x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/focused_trace_waterfall_embeddable.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/focused_trace_waterfall/focused_trace_waterfall_renderer.tsx
similarity index 64%
rename from x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/focused_trace_waterfall_embeddable.tsx
rename to x-pack/solutions/observability/plugins/apm/public/components/shared/focused_trace_waterfall/focused_trace_waterfall_renderer.tsx
index 78079947003c8..3fbc3a589a8bf 100644
--- a/x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/focused_trace_waterfall_embeddable.tsx
+++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/focused_trace_waterfall/focused_trace_waterfall_renderer.tsx
@@ -4,19 +4,25 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { EuiCallOut } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
import React from 'react';
-import { FocusedTraceWaterfall } from '../../components/shared/focused_trace_waterfall';
-import { isPending, useFetcher } from '../../hooks/use_fetcher';
-import { Loading } from './loading';
-import type { ApmTraceWaterfallEmbeddableFocusedProps } from './react_embeddable_factory';
-export function FocusedTraceWaterfallEmbeddable({
- rangeFrom,
- rangeTo,
- traceId,
- docId,
-}: Omit) {
+import { i18n } from '@kbn/i18n';
+import type { FocusedTraceWaterfallProps } from '@kbn/apm-types';
+import { EuiCallOut } from '@elastic/eui';
+import type { CoreStart } from '@kbn/core/public';
+import useEffectOnce from 'react-use/lib/useEffectOnce';
+import { isPending, useFetcher } from '../../../hooks/use_fetcher';
+import { FocusedTraceWaterfall } from '.';
+import { Loading } from '../trace_waterfall/loading';
+import { createCallApmApi } from '../../../services/rest/create_call_apm_api';
+
+interface Props extends FocusedTraceWaterfallProps {
+ core: CoreStart;
+}
+
+export function FocusedTraceWaterfallRenderer({ traceId, rangeFrom, rangeTo, docId, core }: Props) {
+ useEffectOnce(() => {
+ createCallApmApi(core);
+ });
const { data, status } = useFetcher(
(callApmApi) => {
return callApmApi('GET /internal/apm/unified_traces/{traceId}/summary', {
diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/focused_trace_waterfall/lazy_create_focused_trace_waterfall_renderer.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/focused_trace_waterfall/lazy_create_focused_trace_waterfall_renderer.tsx
new file mode 100644
index 0000000000000..448e89f41ee4a
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/focused_trace_waterfall/lazy_create_focused_trace_waterfall_renderer.tsx
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React from 'react';
+
+import { dynamic } from '@kbn/shared-ux-utility';
+import type { CoreStart } from '@kbn/core/public';
+import type { FocusedTraceWaterfallProps } from '@kbn/apm-types';
+
+const LazyFocusedTraceWaterfallRendererComponent = dynamic(() =>
+ import('./focused_trace_waterfall_renderer').then((mod) => ({
+ default: mod.FocusedTraceWaterfallRenderer,
+ }))
+);
+
+export function createLazyFocusedTraceWaterfallRenderer({ core }: { core: CoreStart }) {
+ return (props: FocusedTraceWaterfallProps) => {
+ return ;
+ };
+}
diff --git a/x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/trace_waterfall_embeddable.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/trace_waterfall/full_trace_waterfall_renderer.tsx
similarity index 72%
rename from x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/trace_waterfall_embeddable.tsx
rename to x-pack/solutions/observability/plugins/apm/public/components/shared/trace_waterfall/full_trace_waterfall_renderer.tsx
index 1d83955991516..5e7af44a66bfb 100644
--- a/x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/trace_waterfall_embeddable.tsx
+++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/trace_waterfall/full_trace_waterfall_renderer.tsx
@@ -4,27 +4,35 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React from 'react';
+
import { EuiCallOut } from '@elastic/eui';
+import type { FullTraceWaterfallProps } from '@kbn/apm-types';
import { i18n } from '@kbn/i18n';
-import { isPending, useFetcher } from '../../hooks/use_fetcher';
+import React from 'react';
+import type { CoreStart } from '@kbn/core/public';
+import useEffectOnce from 'react-use/lib/useEffectOnce';
+import { TraceWaterfall } from '.';
+import { isPending, useFetcher } from '../../../hooks/use_fetcher';
import { Loading } from './loading';
-import type { ApmTraceWaterfallEmbeddableEntryProps } from './react_embeddable_factory';
-import { TraceWaterfall } from '../../components/shared/trace_waterfall';
+import { createCallApmApi } from '../../../services/rest/create_call_apm_api';
-export function TraceWaterfallEmbeddable({
- serviceName,
+interface Props extends FullTraceWaterfallProps {
+ core: CoreStart;
+}
+
+export function FullTraceWaterfallRenderer({
+ traceId,
rangeFrom,
rangeTo,
- traceId,
+ serviceName,
scrollElement,
onNodeClick,
- getRelatedErrorsHref,
onErrorClick,
- mode,
-}: ApmTraceWaterfallEmbeddableEntryProps) {
- const isFiltered = mode === 'filtered';
-
+ core,
+}: Props) {
+ useEffectOnce(() => {
+ createCallApmApi(core);
+ });
const { data, status } = useFetcher(
(callApmApi) => {
return callApmApi('GET /internal/apm/unified_traces/{traceId}', {
@@ -33,12 +41,11 @@ export function TraceWaterfallEmbeddable({
query: {
start: rangeFrom,
end: rangeTo,
- serviceName: isFiltered ? serviceName : undefined,
},
},
});
},
- [rangeFrom, rangeTo, traceId, isFiltered, serviceName]
+ [rangeFrom, rangeTo, traceId]
);
if (isPending(status)) {
@@ -65,12 +72,10 @@ export function TraceWaterfallEmbeddable({
errors={data.errors}
onClick={onNodeClick}
scrollElement={scrollElement}
- getRelatedErrorsHref={getRelatedErrorsHref}
isEmbeddable
showLegend
serviceName={serviceName}
onErrorClick={onErrorClick}
- isFiltered={isFiltered}
agentMarks={data.agentMarks}
showCriticalPathControl
/>
diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/trace_waterfall/lazy_create_full_trace_waterfall_renderer.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/trace_waterfall/lazy_create_full_trace_waterfall_renderer.tsx
new file mode 100644
index 0000000000000..af8263d083067
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/trace_waterfall/lazy_create_full_trace_waterfall_renderer.tsx
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React from 'react';
+import { dynamic } from '@kbn/shared-ux-utility';
+import type { CoreStart } from '@kbn/core/public';
+import type { FullTraceWaterfallProps } from '@kbn/apm-types';
+
+const LazyFullTraceWaterfallRendererComponent = dynamic(() =>
+ import('./full_trace_waterfall_renderer').then((mod) => ({
+ default: mod.FullTraceWaterfallRenderer,
+ }))
+);
+
+export function createLazyFullTraceWaterfallRenderer({ core }: { core: CoreStart }) {
+ return (props: FullTraceWaterfallProps) => {
+ return ;
+ };
+}
diff --git a/x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/loading.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/trace_waterfall/loading.tsx
similarity index 100%
rename from x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/loading.tsx
rename to x-pack/solutions/observability/plugins/apm/public/components/shared/trace_waterfall/loading.tsx
diff --git a/x-pack/solutions/observability/plugins/apm/public/embeddable/register_embeddables.tsx b/x-pack/solutions/observability/plugins/apm/public/embeddable/register_embeddables.tsx
index 2823e62a321f6..60627b81c8da0 100644
--- a/x-pack/solutions/observability/plugins/apm/public/embeddable/register_embeddables.tsx
+++ b/x-pack/solutions/observability/plugins/apm/public/embeddable/register_embeddables.tsx
@@ -13,7 +13,6 @@ import {
APM_ALERTING_LATENCY_CHART_EMBEDDABLE,
APM_ALERTING_THROUGHPUT_CHART_EMBEDDABLE,
} from './alerting/constants';
-import { APM_TRACE_WATERFALL_EMBEDDABLE } from './trace_waterfall/constant';
export async function registerEmbeddables(
deps: Omit
@@ -50,12 +49,4 @@ export async function registerEmbeddables(
pluginsStart,
});
});
-
- registerReactEmbeddableFactory(APM_TRACE_WATERFALL_EMBEDDABLE, async () => {
- const { getApmTraceWaterfallEmbeddableFactory } = await import(
- './trace_waterfall/react_embeddable_factory'
- );
-
- return getApmTraceWaterfallEmbeddableFactory({ ...deps, coreStart, pluginsStart });
- });
}
diff --git a/x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/react_embeddable_factory.tsx b/x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/react_embeddable_factory.tsx
deleted file mode 100644
index 79464828356d4..0000000000000
--- a/x-pack/solutions/observability/plugins/apm/public/embeddable/trace_waterfall/react_embeddable_factory.tsx
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-import type { DefaultEmbeddableApi, EmbeddableFactory } from '@kbn/embeddable-plugin/public';
-import type { SerializedTitles } from '@kbn/presentation-publishing';
-import {
- initializeTitleManager,
- titleComparators,
- useBatchedPublishingSubjects,
-} from '@kbn/presentation-publishing';
-import React from 'react';
-import { BehaviorSubject, map, merge } from 'rxjs';
-import { initializeUnsavedChanges } from '@kbn/presentation-containers';
-import { KibanaSectionErrorBoundary } from '@kbn/shared-ux-error-boundary';
-import { i18n } from '@kbn/i18n';
-import type { IWaterfallGetRelatedErrorsHref } from '../../../common/waterfall/typings';
-import { ApmEmbeddableContext } from '../embeddable_context';
-import type { EmbeddableDeps } from '../types';
-import { APM_TRACE_WATERFALL_EMBEDDABLE } from './constant';
-import { TraceWaterfallEmbeddable } from './trace_waterfall_embeddable';
-import { FocusedTraceWaterfallEmbeddable } from './focused_trace_waterfall_embeddable';
-import type { OnErrorClick } from '../../components/shared/trace_waterfall/trace_waterfall_context';
-
-interface BaseProps {
- traceId: string;
- rangeFrom: string;
- rangeTo: string;
-}
-
-export interface ApmTraceWaterfallEmbeddableFocusedProps extends BaseProps, SerializedTitles {
- docId: string;
- mode: 'summary';
-}
-
-export interface ApmTraceWaterfallEmbeddableEntryProps extends BaseProps, SerializedTitles {
- serviceName: string;
- displayLimit?: number;
- scrollElement?: Element;
- onNodeClick?: (nodeSpanId: string) => void;
- getRelatedErrorsHref?: IWaterfallGetRelatedErrorsHref;
- onErrorClick?: OnErrorClick;
- mode: 'full' | 'filtered';
-}
-
-export type ApmTraceWaterfallEmbeddableProps =
- | ApmTraceWaterfallEmbeddableFocusedProps
- | ApmTraceWaterfallEmbeddableEntryProps;
-
-export const getApmTraceWaterfallEmbeddableFactory = (deps: EmbeddableDeps) => {
- const factory: EmbeddableFactory<
- ApmTraceWaterfallEmbeddableProps,
- DefaultEmbeddableApi
- > = {
- type: APM_TRACE_WATERFALL_EMBEDDABLE,
- buildEmbeddable: async ({ initialState, finalizeApi, uuid, parentApi }) => {
- const state = initialState;
- const titleManager = initializeTitleManager(state);
- const serviceName$ = new BehaviorSubject('serviceName' in state ? state.serviceName : '');
- const traceId$ = new BehaviorSubject(state.traceId);
- const rangeFrom$ = new BehaviorSubject(state.rangeFrom);
- const rangeTo$ = new BehaviorSubject(state.rangeTo);
- const mode$ = new BehaviorSubject(state.mode);
- const displayLimit$ = new BehaviorSubject('displayLimit' in state ? state.displayLimit : 0);
- const docId$ = new BehaviorSubject('docId' in state ? state.docId : '');
- const scrollElement$ = new BehaviorSubject(
- 'scrollElement' in state ? state.scrollElement : undefined
- );
- const onNodeClick$ = new BehaviorSubject(
- 'onNodeClick' in state ? state.onNodeClick : undefined
- );
- const getRelatedErrorsHref$ = new BehaviorSubject(
- 'getRelatedErrorsHref' in state ? state.getRelatedErrorsHref : undefined
- );
- const onErrorClick$ = new BehaviorSubject(
- 'onErrorClick' in state ? state.onErrorClick : undefined
- );
-
- function serializeState() {
- return {
- ...titleManager.getLatestState(),
- serviceName: serviceName$.getValue(),
- traceId: traceId$.getValue(),
- rangeFrom: rangeFrom$.getValue(),
- rangeTo: rangeTo$.getValue(),
- displayLimit: displayLimit$.getValue(),
- docId: docId$.getValue(),
- scrollElement: scrollElement$.getValue(),
- onNodeClick: onNodeClick$.getValue(),
- getRelatedErrorsHref: getRelatedErrorsHref$.getValue(),
- onErrorClick: onErrorClick$.getValue(),
- mode: mode$.getValue(),
- };
- }
-
- const unsavedChangesApi = initializeUnsavedChanges({
- uuid,
- parentApi,
- serializeState,
- anyStateChange$: merge(
- titleManager.anyStateChange$,
- serviceName$,
- traceId$,
- rangeFrom$,
- rangeTo$,
- displayLimit$,
- docId$,
- scrollElement$,
- onNodeClick$,
- getRelatedErrorsHref$,
- onErrorClick$,
- mode$
- ).pipe(map(() => undefined)),
- getComparators: () => {
- return {
- ...titleComparators,
- serviceName: 'referenceEquality',
- traceId: 'referenceEquality',
- rangeFrom: 'referenceEquality',
- rangeTo: 'referenceEquality',
- displayLimit: 'referenceEquality',
- docId: 'referenceEquality',
- scrollElement: 'referenceEquality',
- onNodeClick: 'referenceEquality',
- getRelatedErrorsHref: 'referenceEquality',
- onErrorClick: 'referenceEquality',
- mode: 'referenceEquality',
- };
- },
- onReset: (lastSaved) => {
- titleManager.reinitializeState(lastSaved);
-
- // reset base state
- traceId$.next(lastSaved?.traceId ?? '');
- rangeFrom$.next(lastSaved?.rangeFrom ?? '');
- rangeTo$.next(lastSaved?.rangeTo ?? '');
- mode$.next(lastSaved?.mode ?? 'summary');
-
- // reset entry state
- const entryState = lastSaved as ApmTraceWaterfallEmbeddableEntryProps;
- serviceName$.next(entryState?.serviceName ?? '');
- displayLimit$.next(entryState?.displayLimit ?? 0);
- scrollElement$.next(entryState?.scrollElement ?? undefined);
- onNodeClick$.next(entryState?.onNodeClick ?? undefined);
- getRelatedErrorsHref$.next(entryState?.getRelatedErrorsHref ?? undefined);
- onErrorClick$.next(entryState?.onErrorClick ?? undefined);
-
- // reset focused state
- const focusedState = lastSaved as ApmTraceWaterfallEmbeddableFocusedProps;
- docId$.next(focusedState?.docId ?? '');
- },
- });
-
- const api = finalizeApi({
- ...unsavedChangesApi,
- ...titleManager.api,
- serializeState,
- });
-
- return {
- api,
- Component: () => {
- const [
- serviceName,
- traceId,
- rangeFrom,
- rangeTo,
- displayLimit,
- docId,
- scrollElement,
- onNodeClick,
- getRelatedErrorsHref,
- onErrorClick,
- mode,
- ] = useBatchedPublishingSubjects(
- serviceName$,
- traceId$,
- rangeFrom$,
- rangeTo$,
- displayLimit$,
- docId$,
- scrollElement$,
- onNodeClick$,
- getRelatedErrorsHref$,
- onErrorClick$,
- mode$
- );
- const content =
- mode === 'summary' ? (
-
- ) : (
-
- );
-
- return (
-
-
- {content}
-
-
- );
- },
- };
- },
- };
- return factory;
-};
diff --git a/x-pack/solutions/observability/plugins/apm/public/plugin.ts b/x-pack/solutions/observability/plugins/apm/public/plugin.ts
index cd218e5770699..917842cf147d5 100644
--- a/x-pack/solutions/observability/plugins/apm/public/plugin.ts
+++ b/x-pack/solutions/observability/plugins/apm/public/plugin.ts
@@ -100,6 +100,8 @@ import { featureCatalogueEntry } from './feature_catalogue_entry';
import { APMServiceDetailLocator } from './locator/service_detail_locator';
import type { ITelemetryClient } from './services/telemetry';
import { TelemetryService } from './services/telemetry';
+import { createLazyFocusedTraceWaterfallRenderer } from './components/shared/focused_trace_waterfall/lazy_create_focused_trace_waterfall_renderer';
+import { createLazyFullTraceWaterfallRenderer } from './components/shared/trace_waterfall/lazy_create_full_trace_waterfall_renderer';
export type ApmPluginSetup = ReturnType;
export type ApmPluginStart = ReturnType;
@@ -519,7 +521,7 @@ export class ApmPlugin implements Plugin {
}
public start(core: CoreStart, plugins: ApmPluginStartDeps) {
- const { fleet } = plugins;
+ const { fleet, discoverShared } = plugins;
plugins.observabilityAIAssistant?.service.register(async ({ registerRenderFunction }) => {
const mod = await import('./assistant_functions');
@@ -529,6 +531,16 @@ export class ApmPlugin implements Plugin {
});
});
+ discoverShared.features.registry.register({
+ id: 'observability-focused-trace-waterfall',
+ render: createLazyFocusedTraceWaterfallRenderer({ core }),
+ });
+
+ discoverShared.features.registry.register({
+ id: 'observability-full-trace-waterfall',
+ render: createLazyFullTraceWaterfallRenderer({ core }),
+ });
+
if (fleet) {
const agentEnrollmentExtensionData = getApmEnrollmentFlyoutData();