Skip to content

Commit

Permalink
[Embeddable Rebuild] [O11y] Decouple O11y profiling embeddables from …
Browse files Browse the repository at this point in the history
…legacy embeddable factory (#184466)

Fixes #179290, #179291, #179292, #179293

Part of #167429

## Summary

Decouples the legacy embeddable factory from O11y profiling plugin.

The class-based embeddable factory is being removed in preference to a
[React embeddable
factory](#167429). The profiling
plugin was using the legacy embeddable factory to register profiling
components that could be shared to other observability plugins. While
not strictly enforced, the embeddable registry is expected to be used
_only_ for embedding components into Dashboards and Canvas. The
embeddables registered by the Profiling plugin were never made available
to Dashboards or Canvas. And based on offline discussions with
Observability leadership, it is not expected that Profiling components
will be made available for embedding on Dashboards or Canvas in the near
future.

In this PR, I have removed the legacy embeddable usage from the
Profiling plugin. The Observability shared plugin now includes a custom
registration method for the profiling plugin to register React
components so they can be shared to other plugins. This avoids any
circular dependency concerns while still allowing other Observability
plugins to "embed" profiling components in their pages.
  • Loading branch information
nickpeihl authored Jun 13, 2024
1 parent 8c7a36f commit 2bb0c75
Show file tree
Hide file tree
Showing 25 changed files with 348 additions and 447 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,32 @@
* 2.0.
*/

import type { BaseFlameGraph } from '@kbn/profiling-utils';
import React from 'react';
import { ProfilingEmbeddable } from './profiling_embeddable';
import { css } from '@emotion/react';
import type { BaseFlameGraph } from '@kbn/profiling-utils';
import { EMBEDDABLE_FLAMEGRAPH } from '.';
import { getProfilingComponent } from '../helpers/component_registry';

interface Props {
data?: BaseFlameGraph;
isLoading: boolean;
height?: string;
}

export function EmbeddableFlamegraph(props: Props) {
return <ProfilingEmbeddable {...props} embeddableFactoryId={EMBEDDABLE_FLAMEGRAPH} />;
export function EmbeddableFlamegraph({ height, ...props }: Props) {
const EmbeddableFlamegraphComponent = getProfilingComponent<Props>(EMBEDDABLE_FLAMEGRAPH);
return (
<div
css={css`
width: 100%;
height: ${height};
display: flex;
flex: 1 1 100%;
z-index: 1;
min-height: 0;
`}
>
{EmbeddableFlamegraphComponent && <EmbeddableFlamegraphComponent {...props} />}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
* 2.0.
*/

import type { TopNFunctions } from '@kbn/profiling-utils';
import React from 'react';
import { css } from '@emotion/react';
import type { TopNFunctions } from '@kbn/profiling-utils';
import { EMBEDDABLE_FUNCTIONS } from '.';
import { ProfilingEmbeddable } from './profiling_embeddable';
import { getProfilingComponent } from '../helpers/component_registry';

interface Props {
data?: TopNFunctions;
Expand All @@ -18,5 +19,18 @@ interface Props {
}

export function EmbeddableFunctions(props: Props) {
return <ProfilingEmbeddable {...props} embeddableFactoryId={EMBEDDABLE_FUNCTIONS} />;
const EmbeddableFunctionsComponent = getProfilingComponent<Props>(EMBEDDABLE_FUNCTIONS);
return (
<div
css={css`
width: 100%;
display: flex;
flex: 1 1 100%;
z-index: 1;
min-height: 0;
`}
>
{EmbeddableFunctionsComponent && <EmbeddableFunctionsComponent {...props} />}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
* 2.0.
*/

import React from 'react';
import { css } from '@emotion/react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { default as React, useEffect, useRef, useState } from 'react';
import { EMBEDDABLE_PROFILING_SEARCH_BAR } from '.';
import { ObservabilitySharedStart } from '../../../plugin';
import { getProfilingComponent } from '../helpers/component_registry';

export interface EmbeddableProfilingSearchBarProps {
kuery: string;
Expand All @@ -24,38 +23,8 @@ export interface EmbeddableProfilingSearchBarProps {
}

export function EmbeddableProfilingSearchBar(props: EmbeddableProfilingSearchBarProps) {
const { embeddable: embeddablePlugin } = useKibana<ObservabilitySharedStart>().services;
const [embeddable, setEmbeddable] = useState<any>();
const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);

useEffect(() => {
async function createEmbeddable() {
const factory = embeddablePlugin?.getEmbeddableFactory(EMBEDDABLE_PROFILING_SEARCH_BAR);
const input = {
id: 'embeddable_profiling',
};
const embeddableObject = await factory?.create(input);
setEmbeddable(embeddableObject);
}
createEmbeddable();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (embeddableRoot.current && embeddable) {
embeddable.render(embeddableRoot.current);
}
}, [embeddable, embeddableRoot]);

useEffect(() => {
if (embeddable) {
embeddable.updateInput({
...props,
});
embeddable.reload();
}
}, [embeddable, props]);

const EmbeddableProfilingSearchBarComponent =
getProfilingComponent<EmbeddableProfilingSearchBarProps>(EMBEDDABLE_PROFILING_SEARCH_BAR);
return (
<div
css={css`
Expand All @@ -65,7 +34,10 @@ export function EmbeddableProfilingSearchBar(props: EmbeddableProfilingSearchBar
z-index: 1;
min-height: 0;
`}
ref={embeddableRoot}
/>
>
{EmbeddableProfilingSearchBarComponent && (
<EmbeddableProfilingSearchBarComponent {...props} />
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
*/

import React from 'react';
import { css } from '@emotion/react';
import { TopNType } from '@kbn/profiling-utils';
import { EMBEDDABLE_STACK_TRACES } from '.';
import { ProfilingEmbeddable } from './profiling_embeddable';
import { getProfilingComponent } from '../helpers/component_registry';

interface Props {
type: TopNType;
Expand All @@ -20,5 +21,18 @@ interface Props {
}

export function EmbeddableStackTraces(props: Props) {
return <ProfilingEmbeddable {...props} embeddableFactoryId={EMBEDDABLE_STACK_TRACES} />;
const EmbeddableStackTracesComponent = getProfilingComponent<Props>(EMBEDDABLE_STACK_TRACES);
return (
<div
css={css`
width: 100%;
display: flex;
flex: 1 1 100%;
z-index: 1;
min-height: 0;
`}
>
{EmbeddableStackTracesComponent && <EmbeddableStackTracesComponent {...props} />}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 { i18n } from '@kbn/i18n';

const registry: { [key: string]: React.FC<any> } = {};

export const registerProfilingComponent = <T>(key: string, component: React.FC<T>) => {
if (registry[key] !== undefined) {
throw new Error(
i18n.translate('xpack.observabilityShared.profilingComponentAlreadyExists.error', {
defaultMessage: `Component with key {key} already exists`,
values: { key },
})
);
}
registry[key] = component;
};

export const getProfilingComponent = <T>(key: string): React.FC<T> => {
if (registry[key] === undefined) {
throw new Error(
i18n.translate('xpack.observabilityShared.profilingComponentNotFound.error', {
defaultMessage: `Component with key {key} not found`,
values: { key },
})
);
}
return registry[key];
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import { BehaviorSubject } from 'rxjs';
import { createLazyObservabilityPageTemplate } from './components/page_template';
import { createNavigationRegistry } from './components/page_template/helpers/navigation_registry';
import { registerProfilingComponent } from './components/profiling/helpers/component_registry';
import {
type AssetDetailsFlyoutLocator,
AssetDetailsFlyoutLocatorDefinition,
Expand Down Expand Up @@ -102,6 +103,7 @@ export class ObservabilitySharedPlugin implements Plugin {
});

return {
registerProfilingComponent,
locators: this.createLocators(pluginsSetup.share.url),
navigation: {
registerSections: this.navigationRegistry.registerSections,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"observabilityShared",
"unifiedSearch",
"share",
"embeddable",
"profilingDataAccess"
],
"requiredBundles": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,35 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Embeddable, EmbeddableOutput, IContainer } from '@kbn/embeddable-plugin/public';
import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public';
import { createFlameGraph } from '@kbn/profiling-utils';
import { BaseFlameGraph, createFlameGraph } from '@kbn/profiling-utils';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { profilingShowErrorFrames } from '@kbn/observability-plugin/common';
import { FlameGraph } from '../../components/flamegraph';
import { AsyncEmbeddableComponent } from '../async_embeddable_component';
import {
ProfilingEmbeddableProvider,
ProfilingEmbeddablesDependencies,
} from '../profiling_embeddable_provider';
import { EmbeddableFlamegraphEmbeddableInput } from './embeddable_flamegraph_factory';
import { useProfilingDependencies } from '../../components/contexts/profiling_dependencies/use_profiling_dependencies';

export class EmbeddableFlamegraph extends Embeddable<
EmbeddableFlamegraphEmbeddableInput,
EmbeddableOutput
> {
readonly type = EMBEDDABLE_FLAMEGRAPH;
private _domNode?: HTMLElement;
export type EmbeddableFlamegraphProps = FlamegraphProps & ProfilingEmbeddablesDependencies;

constructor(
private deps: ProfilingEmbeddablesDependencies,
initialInput: EmbeddableFlamegraphEmbeddableInput,
parent?: IContainer
) {
super(initialInput, {}, parent);
}
export type EmbeddableFlamegraphSharedComponent = React.FC<FlamegraphProps>;

render(domNode: HTMLElement) {
this._domNode = domNode;
render(
<ProfilingEmbeddableProvider deps={this.deps}>
<Flamegraph {...this.input} />
</ProfilingEmbeddableProvider>,
domNode
);
}

public destroy() {
if (this._domNode) {
unmountComponentAtNode(this._domNode);
}
}
export interface FlamegraphProps {
data?: BaseFlameGraph;
isLoading: boolean;
}

reload() {
if (this._domNode) {
this.render(this._domNode);
}
}
export function EmbeddableFlamegraph({ data, isLoading, ...deps }: EmbeddableFlamegraphProps) {
return (
<ProfilingEmbeddableProvider deps={deps}>
<Flamegraph isLoading={isLoading} data={data} />
</ProfilingEmbeddableProvider>
);
}

function Flamegraph({ isLoading, data }: EmbeddableFlamegraphEmbeddableInput) {
function Flamegraph({ isLoading, data }: FlamegraphProps) {
const { core } = useProfilingDependencies().start;
const showErrorFrames = core.uiSettings.get<boolean>(profilingShowErrorFrames);
const flamegraph = !isLoading && data ? createFlameGraph(data, showErrorFrames) : undefined;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { EmbeddableFlamegraphSharedComponent, FlamegraphProps } from './embeddable_flamegraph';
import { ProfilingEmbeddablesDependencies } from '../profiling_embeddable_provider';

const LazyEmbeddableFlamegraph = dynamic(async () => {
const Component = await import('./embeddable_flamegraph');
return { default: Component.EmbeddableFlamegraph };
});

export const getEmbeddableFlamegraphComponent = (
profilingEmbeddableDependencies: ProfilingEmbeddablesDependencies
): EmbeddableFlamegraphSharedComponent => {
return (props: FlamegraphProps) => {
return <LazyEmbeddableFlamegraph {...props} {...profilingEmbeddableDependencies} />;
};
};
Loading

0 comments on commit 2bb0c75

Please sign in to comment.