Canvas add from library react embeddables (#183089)

Fixes #182619 

Add support for React embeddables in the Canvas Add from Library flyout.

I tested this against the [React Map embeddable
PR](https://github.com/elastic/kibana/pull/178158).
This commit is contained in:
Nick Peihl 2024-05-10 13:29:59 -04:00 committed by GitHub
parent fef94d7d34
commit e5ff8743d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 104 additions and 34 deletions

View file

@ -25,6 +25,7 @@ export {
EmbeddableStateTransfer,
ErrorEmbeddable,
genericEmbeddableInputIsEqual,
getReactEmbeddableSavedObjects,
isContextMenuTriggerContext,
isEmbeddable,
isErrorEmbeddable,
@ -79,6 +80,7 @@ export type {
PanelState,
PropertySpec,
RangeSelectContext,
ReactEmbeddableSavedObject,
ReferenceOrValueEmbeddable,
SavedObjectEmbeddableInput,
SelfStyledEmbeddable,

View file

@ -46,30 +46,39 @@ const renderReactEmbeddable = ({
input,
container,
handlers,
core,
}: {
type: string;
uuid: string;
input: EmbeddableInput;
container: CanvasContainerApi;
handlers: RendererHandlers;
core: CoreStart;
}) => {
return (
<ReactEmbeddableRenderer
type={type}
maybeId={uuid}
parentApi={container as unknown as PresentationContainer}
key={`${type}_${uuid}`}
state={{ rawState: input }}
onAnyStateChange={(newState) => {
const newExpression = embeddableInputToExpression(
newState.rawState as unknown as EmbeddableInput,
type,
undefined,
true
);
if (newExpression) handlers.onEmbeddableInputChange(newExpression);
}}
/>
<KibanaRenderContextProvider {...core}>
<div
className={CANVAS_EMBEDDABLE_CLASSNAME}
style={{ width: '100%', height: '100%', cursor: 'auto' }}
>
<ReactEmbeddableRenderer
type={type}
maybeId={uuid}
parentApi={container as unknown as PresentationContainer}
key={`${type}_${uuid}`}
state={{ rawState: input }}
onAnyStateChange={(newState) => {
const newExpression = embeddableInputToExpression(
newState.rawState as unknown as EmbeddableInput,
type,
undefined,
true
);
if (newExpression) handlers.onEmbeddableInputChange(newExpression);
}}
/>
</div>
</KibanaRenderContextProvider>
);
};
@ -138,6 +147,7 @@ export const embeddableRendererFactory = (
uuid: uniqueId,
type: embeddableType,
container: canvasApi,
core,
}),
domNode,
() => handlers.done()

View file

@ -5,11 +5,17 @@
* 2.0.
*/
import React, { FC, useCallback } from 'react';
import React, { FC, useCallback, useMemo } from 'react';
import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { SavedObjectFinder, SavedObjectMetaData } from '@kbn/saved-objects-finder-plugin/public';
import { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
import {
EmbeddableFactory,
ReactEmbeddableSavedObject,
getReactEmbeddableSavedObjects,
} from '@kbn/embeddable-plugin/public';
import { useEmbeddablesService, usePlatformService } from '../../services';
const strings = {
@ -22,6 +28,14 @@ const strings = {
defaultMessage: 'Add from library',
}),
};
interface LegacyFactoryMap {
[key: string]: EmbeddableFactory;
}
interface FactoryMap<TSavedObjectAttributes extends FinderAttributes = FinderAttributes> {
[key: string]: ReactEmbeddableSavedObject<TSavedObjectAttributes> & { type: string };
}
export interface Props {
onClose: () => void;
onSelect: (id: string, embeddableType: string, isByValueEnabled?: boolean) => void;
@ -40,8 +54,67 @@ export const AddEmbeddableFlyout: FC<Props> = ({
const { getEmbeddableFactories } = embeddablesService;
const { getContentManagement, getUISettings } = platformService;
const legacyFactoriesBySavedObjectType: LegacyFactoryMap = useMemo(() => {
return [...getEmbeddableFactories()]
.filter(
(embeddableFactory) =>
Boolean(embeddableFactory.savedObjectMetaData?.type) && !embeddableFactory.isContainerType
)
.reduce((acc, factory) => {
acc[factory.savedObjectMetaData!.type] = factory;
return acc;
}, {} as LegacyFactoryMap);
}, [getEmbeddableFactories]);
const factoriesBySavedObjectType: FactoryMap = useMemo(() => {
return [...getReactEmbeddableSavedObjects()]
.filter(([type, embeddableFactory]) => {
return Boolean(embeddableFactory.savedObjectMetaData?.type);
})
.reduce((acc, [type, factory]) => {
acc[factory.savedObjectMetaData!.type] = {
...factory,
type,
};
return acc;
}, {} as FactoryMap);
}, []);
const metaData = useMemo(
() =>
[
...Object.values(factoriesBySavedObjectType),
...Object.values(legacyFactoriesBySavedObjectType),
]
.filter((factory) =>
Boolean(
factory.type !== 'links' && // Links panels only exist on Dashboards
(isByValueEnabled || availableEmbeddables.includes(factory.type))
)
)
.map((factory) => factory.savedObjectMetaData)
.filter<SavedObjectMetaData<{}>>(function (
maybeSavedObjectMetaData
): maybeSavedObjectMetaData is SavedObjectMetaData<{}> {
return maybeSavedObjectMetaData !== undefined;
})
.sort((a, b) => a.type.localeCompare(b.type)),
[
availableEmbeddables,
factoriesBySavedObjectType,
isByValueEnabled,
legacyFactoriesBySavedObjectType,
]
);
const onAddPanel = useCallback(
(id: string, savedObjectType: string) => {
if (factoriesBySavedObjectType[savedObjectType]) {
const factory = factoriesBySavedObjectType[savedObjectType];
const { type } = factory;
onSelect(id, type, isByValueEnabled);
return;
}
const embeddableFactories = getEmbeddableFactories();
// Find the embeddable type from the saved object type
const found = Array.from(embeddableFactories).find((embeddableFactory) => {
@ -55,24 +128,9 @@ export const AddEmbeddableFlyout: FC<Props> = ({
onSelect(id, foundEmbeddableType, isByValueEnabled);
},
[isByValueEnabled, getEmbeddableFactories, onSelect]
[isByValueEnabled, getEmbeddableFactories, onSelect, factoriesBySavedObjectType]
);
const embeddableFactories = getEmbeddableFactories();
const availableSavedObjects = Array.from(embeddableFactories)
.filter(
(factory) =>
factory.type !== 'links' && // Links panels only exist on Dashboards
(isByValueEnabled || availableEmbeddables.includes(factory.type))
)
.map((factory) => factory.savedObjectMetaData)
.filter<SavedObjectMetaData<{}>>(function (
maybeSavedObjectMetaData
): maybeSavedObjectMetaData is SavedObjectMetaData<{}> {
return maybeSavedObjectMetaData !== undefined;
});
return (
<EuiFlyout ownFocus onClose={onClose} data-test-subj="dashboardAddPanel">
<EuiFlyoutHeader hasBorder>
@ -83,7 +141,7 @@ export const AddEmbeddableFlyout: FC<Props> = ({
<EuiFlyoutBody>
<SavedObjectFinder
onChoose={onAddPanel}
savedObjectMetaData={availableSavedObjects}
savedObjectMetaData={metaData}
showFilter={true}
noItemsMessage={strings.getNoItemsText()}
services={{