diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.ts b/.buildkite/scripts/steps/storybooks/build_and_upload.ts index b1135490a2023..52aaa7e087a09 100644 --- a/.buildkite/scripts/steps/storybooks/build_and_upload.ts +++ b/.buildkite/scripts/steps/storybooks/build_and_upload.ts @@ -26,7 +26,6 @@ const STORYBOOKS = [ 'dashboard_enhanced', 'dashboard', 'data', - 'embeddable', 'esql_editor', 'expression_error', 'expression_image', diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index c89df73f1b877..1d3ec990948a9 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -29,7 +29,6 @@ export const storybookAliases = { dashboard: 'src/plugins/dashboard/.storybook', data: 'src/plugins/data/.storybook', discover: 'src/plugins/discover/.storybook', - embeddable: 'src/plugins/embeddable/.storybook', esql_ast_inspector: 'examples/esql_ast_inspector/.storybook', es_ui_shared: 'src/plugins/es_ui_shared/.storybook', expandable_flyout: 'packages/kbn-expandable-flyout/.storybook', diff --git a/src/plugins/embeddable/.storybook/decorator.tsx b/src/plugins/embeddable/.storybook/decorator.tsx deleted file mode 100644 index 936050352d22a..0000000000000 --- a/src/plugins/embeddable/.storybook/decorator.tsx +++ /dev/null @@ -1,24 +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 { DecoratorFn } from '@storybook/react'; -import { I18nProvider } from '@kbn/i18n-react'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; - -export const servicesContextDecorator: DecoratorFn = (story, { globals }) => { - const darkMode = ['v8.dark', 'v7.dark'].includes(globals.euiTheme); - - return ( - - {story()} - - ); -}; diff --git a/src/plugins/embeddable/.storybook/main.ts b/src/plugins/embeddable/.storybook/main.ts deleted file mode 100644 index 02a7d1b2dd5e3..0000000000000 --- a/src/plugins/embeddable/.storybook/main.ts +++ /dev/null @@ -1,11 +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". - */ - -// eslint-disable-next-line import/no-default-export -export { defaultConfig as default } from '@kbn/storybook'; diff --git a/src/plugins/embeddable/.storybook/manager.ts b/src/plugins/embeddable/.storybook/manager.ts deleted file mode 100644 index b28080883ec14..0000000000000 --- a/src/plugins/embeddable/.storybook/manager.ts +++ /dev/null @@ -1,22 +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 { addons } from '@storybook/addons'; -import { create } from '@storybook/theming'; -import { PANEL_ID } from '@storybook/addon-actions'; - -addons.setConfig({ - theme: create({ - base: 'light', - brandTitle: 'Kibana Embeddable Storybook', - brandUrl: 'https://github.com/elastic/kibana/tree/main/src/plugins/embeddable', - }), - showPanel: true.valueOf, - selectedPanel: PANEL_ID, -}); diff --git a/src/plugins/embeddable/.storybook/preview.tsx b/src/plugins/embeddable/.storybook/preview.tsx deleted file mode 100644 index 4ddf9e5080d29..0000000000000 --- a/src/plugins/embeddable/.storybook/preview.tsx +++ /dev/null @@ -1,30 +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 { addDecorator } from '@storybook/react'; -import { Title, Subtitle, Description, Primary, Stories } from '@storybook/addon-docs/blocks'; - -import { servicesContextDecorator } from './decorator'; - -addDecorator(servicesContextDecorator); - -export const parameters = { - docs: { - page: () => ( - <> - - <Subtitle /> - <Description /> - <Primary /> - <Stories /> - </> - ), - }, -}; diff --git a/src/plugins/embeddable/public/__stories__/embeddable_panel.stories.tsx b/src/plugins/embeddable/public/__stories__/embeddable_panel.stories.tsx deleted file mode 100644 index bcb0af1cf9064..0000000000000 --- a/src/plugins/embeddable/public/__stories__/embeddable_panel.stories.tsx +++ /dev/null @@ -1,279 +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, { - forwardRef, - useCallback, - useContext, - useEffect, - useImperativeHandle, - useMemo, - useRef, -} from 'react'; -import { ReplaySubject } from 'rxjs'; -import { ThemeContext } from '@emotion/react'; -import { DecoratorFn, Meta } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { CoreTheme } from '@kbn/core-theme-browser'; -import type { Action } from '@kbn/ui-actions-plugin/public'; - -import { CONTEXT_MENU_TRIGGER, EmbeddablePanel, PANEL_BADGE_TRIGGER, ViewMode } from '..'; -import { actions } from '../store'; -import { HelloWorldEmbeddable } from './hello_world_embeddable'; - -const layout: DecoratorFn = (story) => { - return ( - <EuiFlexGroup direction="row" justifyContent="center"> - <EuiFlexItem grow={false} style={{ height: 300, width: 500 }}> - {story()} - </EuiFlexItem> - </EuiFlexGroup> - ); -}; - -export default { - title: 'components/EmbeddablePanel', - argTypes: { - hideHeader: { - name: 'Hide Header', - control: { type: 'boolean' }, - }, - loading: { - name: 'Loading', - control: { type: 'boolean' }, - }, - showShadow: { - name: 'Show Shadow', - control: { type: 'boolean' }, - }, - title: { - name: 'Title', - control: { type: 'text' }, - }, - viewMode: { - name: 'View Mode', - control: { type: 'boolean' }, - }, - }, - decorators: [layout], -} as Meta; - -interface HelloWorldEmbeddablePanelProps { - getActions?(type: string): Promise<Action[]>; - hideHeader: boolean; - loading: boolean; - showShadow: boolean; - showBorder: boolean; - title: string; - viewMode: boolean; -} - -const HelloWorldEmbeddablePanel = forwardRef< - { embeddable: HelloWorldEmbeddable }, - HelloWorldEmbeddablePanelProps ->( - ( - { - getActions, - hideHeader, - loading, - showShadow, - showBorder, - title, - viewMode, - }: HelloWorldEmbeddablePanelProps, - ref - ) => { - const embeddable = useMemo(() => new HelloWorldEmbeddable({ id: `${Math.random()}` }, {}), []); - const theme$ = useMemo(() => new ReplaySubject<CoreTheme>(1), []); - const theme = useContext(ThemeContext) as CoreTheme; - - useEffect(() => theme$.next(theme), [theme$, theme]); - useEffect(() => { - embeddable.store.dispatch(actions.input.setTitle(title)); - }, [embeddable.store, title]); - useEffect(() => { - embeddable.store.dispatch( - actions.input.setViewMode(viewMode ? ViewMode.VIEW : ViewMode.EDIT) - ); - }, [embeddable.store, viewMode]); - useEffect( - () => void embeddable.store.dispatch(actions.output.setLoading(loading)), - [embeddable, loading] - ); - useImperativeHandle(ref, () => ({ embeddable })); - - return ( - <EmbeddablePanel - embeddable={embeddable} - getActions={getActions} - hideHeader={hideHeader} - showShadow={showShadow} - showBorder={showBorder} - /> - ); - } -); - -export const Default = HelloWorldEmbeddablePanel as Meta<HelloWorldEmbeddablePanelProps>; - -Default.args = { - hideHeader: false, - loading: false, - showShadow: false, - showBorder: false, - title: 'Hello World', - viewMode: true, -}; - -interface DefaultWithBadgesProps extends HelloWorldEmbeddablePanelProps { - badges: string[]; -} - -export function DefaultWithBadges({ badges, ...props }: DefaultWithBadgesProps) { - const getActions = useCallback( - async (type: string) => { - switch (type) { - case PANEL_BADGE_TRIGGER: - return ( - badges?.map<Action>((badge, id) => ({ - execute: async (...args) => action(`onClick(${badge})`)(...args), - getDisplayName: () => badge, - getIconType: () => ['help', 'search', undefined][id % 3], - id: `${id}`, - isCompatible: async () => true, - type: '', - })) ?? [] - ); - default: - return []; - } - }, - [badges] - ); - const ref = useRef<React.ComponentRef<typeof HelloWorldEmbeddablePanel>>(null); - - useEffect( - () => - void ref.current?.embeddable.store.dispatch( - actions.input.setLastReloadRequestTime(new Date().getMilliseconds()) - ), - [getActions] - ); - - return <HelloWorldEmbeddablePanel ref={ref} {...props} getActions={getActions} />; -} - -DefaultWithBadges.args = { - ...Default.args, - badges: ['Help', 'Search', 'Something'], -}; - -DefaultWithBadges.argTypes = { - badges: { name: 'Badges' }, -}; - -interface DefaultWithContextMenuProps extends HelloWorldEmbeddablePanelProps { - items: string[]; -} - -export function DefaultWithContextMenu({ items, ...props }: DefaultWithContextMenuProps) { - const getActions = useCallback( - async (type: string) => { - switch (type) { - case CONTEXT_MENU_TRIGGER: - return ( - items?.map<Action>((item, id) => ({ - execute: async (...args) => action(`onClick(${item})`)(...args), - getDisplayName: () => item, - getIconType: () => ['help', 'search', undefined][id % 3], - id: `${id}`, - isCompatible: async () => true, - type: '', - })) ?? [] - ); - default: - return []; - } - }, - [items] - ); - const ref = useRef<React.ComponentRef<typeof HelloWorldEmbeddablePanel>>(null); - - useEffect( - () => - void ref.current?.embeddable.store.dispatch( - actions.input.setLastReloadRequestTime(new Date().getMilliseconds()) - ), - [getActions] - ); - - return <HelloWorldEmbeddablePanel ref={ref} {...props} getActions={getActions} />; -} - -DefaultWithContextMenu.args = { - ...Default.args, - items: ['Help', 'Search', 'Something'], -}; - -DefaultWithContextMenu.argTypes = { - items: { name: 'Context Menu Items' }, -}; - -interface DefaultWithErrorProps extends HelloWorldEmbeddablePanelProps { - message: string; -} - -export function DefaultWithError({ message, ...props }: DefaultWithErrorProps) { - const ref = useRef<React.ComponentRef<typeof HelloWorldEmbeddablePanel>>(null); - - useEffect( - () => void ref.current?.embeddable.store.dispatch(actions.output.setError(new Error(message))), - [message] - ); - - return <HelloWorldEmbeddablePanel ref={ref} {...props} />; -} - -DefaultWithError.args = { - ...Default.args, - message: 'Something went wrong', -}; - -DefaultWithError.argTypes = { - message: { name: 'Message', control: { type: 'text' } }, -}; - -export function DefaultWithCustomError({ message, ...props }: DefaultWithErrorProps) { - const ref = useRef<React.ComponentRef<typeof HelloWorldEmbeddablePanel>>(null); - - useEffect(() => { - if (ref.current) { - ref.current.embeddable.catchError = (error) => { - return <EuiEmptyPrompt iconColor="warning" iconType="bug" body={error.message} />; - }; - } - }, []); - useEffect( - () => void ref.current?.embeddable.store.dispatch(actions.output.setError(new Error(message))), - [message] - ); - - return <HelloWorldEmbeddablePanel ref={ref} {...props} />; -} - -DefaultWithCustomError.args = { - ...Default.args, - message: 'Something went wrong', -}; - -DefaultWithCustomError.argTypes = { - message: { name: 'Message', control: { type: 'text' } }, -}; diff --git a/src/plugins/embeddable/public/__stories__/embeddable_root.stories.tsx b/src/plugins/embeddable/public/__stories__/embeddable_root.stories.tsx deleted file mode 100644 index 8a6addb0a3685..0000000000000 --- a/src/plugins/embeddable/public/__stories__/embeddable_root.stories.tsx +++ /dev/null @@ -1,78 +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, { useMemo } from 'react'; -import { DecoratorFn, Meta } from '@storybook/react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { EmbeddableInput, EmbeddableRoot } from '..'; -import { HelloWorldEmbeddable } from './hello_world_embeddable'; - -const layout: DecoratorFn = (story) => { - return ( - <EuiFlexGroup direction="row" justifyContent="center"> - <EuiFlexItem grow={false} style={{ height: 300, width: 500 }}> - {story()} - </EuiFlexItem> - </EuiFlexGroup> - ); -}; - -export default { - title: 'components/EmbeddableRoot', - argTypes: { - loading: { - name: 'Loading', - control: { type: 'boolean' }, - }, - title: { - name: 'Title', - control: { type: 'text' }, - }, - }, - decorators: [layout], -} as Meta; - -interface DefaultProps { - error?: string; - loading?: boolean; - title: string; -} - -export function Default({ title, ...props }: DefaultProps) { - const id = useMemo(() => `${Math.random()}`, []); - const input = useMemo<EmbeddableInput>( - () => ({ - id, - title, - lastReloadRequestTime: new Date().getMilliseconds(), - }), - [id, title] - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - const embeddable = useMemo(() => new HelloWorldEmbeddable(input, {}), []); - - return <EmbeddableRoot {...props} embeddable={embeddable} input={input} />; -} - -Default.args = { - title: 'Hello World', - loading: false, -}; - -export const DefaultWithError = Default.bind({}) as Meta<DefaultProps>; - -DefaultWithError.args = { - ...Default.args, - error: 'Something went wrong', -}; - -DefaultWithError.argTypes = { - error: { name: 'Error', control: { type: 'text' } }, -}; diff --git a/src/plugins/embeddable/public/__stories__/error_embeddable.stories.tsx b/src/plugins/embeddable/public/__stories__/error_embeddable.stories.tsx deleted file mode 100644 index ebd388e3d31c2..0000000000000 --- a/src/plugins/embeddable/public/__stories__/error_embeddable.stories.tsx +++ /dev/null @@ -1,43 +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 { useEffect, useMemo } from 'react'; -import { Meta } from '@storybook/react'; - -import { ErrorEmbeddable } from '..'; - -export default { - title: 'components/ErrorEmbeddable', - argTypes: { - message: { - name: 'Message', - control: { type: 'text' }, - }, - }, -} as Meta; - -interface ErrorEmbeddableWrapperProps { - message: string; -} - -function ErrorEmbeddableWrapper({ message }: ErrorEmbeddableWrapperProps) { - const embeddable = useMemo( - () => new ErrorEmbeddable(message, { id: `${Math.random()}` }, undefined), - [message] - ); - useEffect(() => () => embeddable.destroy(), [embeddable]); - - return embeddable.render(); -} - -export const Default = ErrorEmbeddableWrapper as Meta<ErrorEmbeddableWrapperProps>; - -Default.args = { - message: 'Something went wrong', -}; diff --git a/src/plugins/embeddable/public/__stories__/hello_world_embeddable.tsx b/src/plugins/embeddable/public/__stories__/hello_world_embeddable.tsx deleted file mode 100644 index ef19adedde943..0000000000000 --- a/src/plugins/embeddable/public/__stories__/hello_world_embeddable.tsx +++ /dev/null @@ -1,33 +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 { connect, Provider } from 'react-redux'; -import { EuiEmptyPrompt } from '@elastic/eui'; -import { Embeddable } from '..'; -import { createStore, State } from '../store'; - -export class HelloWorldEmbeddable extends Embeddable { - // eslint-disable-next-line @kbn/eslint/no_this_in_property_initializers - readonly store = createStore(this); - - readonly type = 'hello-world'; - - reload() {} - - render() { - const HelloWorld = connect((state: State) => ({ body: state.input.title }))(EuiEmptyPrompt); - - return ( - <Provider store={this.store}> - <HelloWorld /> - </Provider> - ); - } -} diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 25a25b2447146..19d3bd76c1a77 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -22,8 +22,6 @@ export { defaultEmbeddableFactoryProvider, Embeddable, EmbeddableFactoryNotFoundError, - EmbeddableRenderer, - EmbeddableRoot, EmbeddableStateTransfer, ErrorEmbeddable, genericEmbeddableInputIsEqual, @@ -52,7 +50,6 @@ export { SELECT_RANGE_TRIGGER, shouldFetch$, shouldRefreshFilterCompareOptions, - useEmbeddableFactory, VALUE_CLICK_TRIGGER, ViewMode, withEmbeddableSubscription, @@ -72,7 +69,6 @@ export type { EmbeddableInstanceConfiguration, EmbeddableOutput, EmbeddablePackageState, - EmbeddableRendererProps, FilterableEmbeddable, IContainer, IEmbeddable, diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx deleted file mode 100644 index a7a6fab89973c..0000000000000 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.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 { waitFor } from '@testing-library/react'; -import { render, renderHook } from '@testing-library/react'; -import { - HelloWorldEmbeddable, - HelloWorldEmbeddableFactoryDefinition, - HELLO_WORLD_EMBEDDABLE, -} from '../../tests/fixtures'; -import { EmbeddableRenderer, useEmbeddableFactory } from './embeddable_renderer'; -import { embeddablePluginMock } from '../../mocks'; - -describe('useEmbeddableFactory', () => { - it('should update upstream value changes', async () => { - const { setup, doStart } = embeddablePluginMock.createInstance(); - const getFactory = setup.registerEmbeddableFactory( - HELLO_WORLD_EMBEDDABLE, - new HelloWorldEmbeddableFactoryDefinition() - ); - doStart(); - - const { result } = renderHook(() => - useEmbeddableFactory({ factory: getFactory(), input: { id: 'hello' } }) - ); - - const [, loading] = result.current; - - expect(loading).toBe(true); - - await waitFor(() => { - const [embeddable] = result.current; - expect(embeddable).toBeDefined(); - }); - }); -}); - -describe('<EmbeddableRenderer/>', () => { - test('Render embeddable', () => { - const embeddable = new HelloWorldEmbeddable({ id: 'hello' }); - const { getByTestId } = render(<EmbeddableRenderer embeddable={embeddable} />); - expect(getByTestId('helloWorldEmbeddable')).toBeInTheDocument(); - }); - - test('Render factory', async () => { - const { setup, doStart } = embeddablePluginMock.createInstance(); - const getFactory = setup.registerEmbeddableFactory( - HELLO_WORLD_EMBEDDABLE, - new HelloWorldEmbeddableFactoryDefinition() - ); - doStart(); - - const { getByTestId, queryByTestId } = render( - <EmbeddableRenderer factory={getFactory()} input={{ id: 'hello' }} /> - ); - expect(getByTestId('embedSpinner')).toBeInTheDocument(); - await waitFor(() => !queryByTestId('embedSpinner')); // wait until spinner disappears - expect(getByTestId('helloWorldEmbeddable')).toBeInTheDocument(); - }); -}); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx deleted file mode 100644 index 14a064827e260..0000000000000 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx +++ /dev/null @@ -1,165 +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, { useEffect, useState } from 'react'; -import { EmbeddableInput, IEmbeddable } from './i_embeddable'; -import { EmbeddableRoot } from './embeddable_root'; -import { EmbeddableFactory } from './embeddable_factory'; -import { ErrorEmbeddable } from './error_embeddable'; -import { isErrorEmbeddable } from './is_error_embeddable'; - -/** - * This type is a publicly exposed props of {@link EmbeddableRenderer} - * Union is used to validate that or factory or embeddable is passed in, but it can't be both simultaneously - * In case when embeddable is passed in, input is optional, because there is already an input inside of embeddable object - * In case when factory is used, then input is required, because it will be used as initial input to create an embeddable object - */ -export type EmbeddableRendererProps<I extends EmbeddableInput> = - | EmbeddableRendererPropsWithEmbeddable<I> - | EmbeddableRendererWithFactory<I>; - -interface EmbeddableRendererPropsWithEmbeddable<I extends EmbeddableInput> { - input?: I; - onInputUpdated?: (newInput: I) => void; - embeddable: IEmbeddable<I>; -} - -interface EmbeddableRendererWithFactory<I extends EmbeddableInput> { - input: I; - onInputUpdated?: (newInput: I) => void; - factory: EmbeddableFactory<I>; -} - -function isWithFactory<I extends EmbeddableInput>( - props: EmbeddableRendererProps<I> -): props is EmbeddableRendererWithFactory<I> { - return 'factory' in props; -} - -export function useEmbeddableFactory<I extends EmbeddableInput>({ - input, - factory, - onInputUpdated, -}: EmbeddableRendererWithFactory<I>) { - const [embeddable, setEmbeddable] = useState<IEmbeddable<I> | ErrorEmbeddable | undefined>( - undefined - ); - const [loading, setLoading] = useState<boolean>(false); - const [error, setError] = useState<string | undefined>(); - const latestInput = React.useRef(input); - useEffect(() => { - latestInput.current = input; - }, [input]); - - useEffect(() => { - let canceled = false; - - // keeping track of embeddables created by this component to be able to destroy them - let createdEmbeddableRef: IEmbeddable | ErrorEmbeddable | undefined; - setEmbeddable(undefined); - setLoading(true); - factory - .create(latestInput.current!) - .then((createdEmbeddable) => { - if (canceled) { - if (createdEmbeddable) { - createdEmbeddable.destroy(); - } - } else { - createdEmbeddableRef = createdEmbeddable; - setEmbeddable(createdEmbeddable); - } - }) - .catch((err) => { - if (canceled) return; - setError(err?.message); - }) - .finally(() => { - if (canceled) return; - setLoading(false); - }); - - return () => { - canceled = true; - if (createdEmbeddableRef) { - createdEmbeddableRef.destroy(); - } - }; - }, [factory]); - - useEffect(() => { - if (!embeddable) return; - if (isErrorEmbeddable(embeddable)) return; - if (!onInputUpdated) return; - const sub = embeddable.getInput$().subscribe((newInput) => { - onInputUpdated(newInput); - }); - return () => { - sub.unsubscribe(); - }; - }, [embeddable, onInputUpdated]); - - return [embeddable, loading, error] as const; -} - -/** - * Helper react component to render an embeddable - * Can be used if you have an embeddable object or an embeddable factory - * Supports updating input by passing `input` prop - * - * @remarks - * This component shouldn't be used inside an embeddable container to render embeddable children - * because children may lose inherited input, here is why: - * - * When passing `input` inside a prop, internally there is a call: - * - * ```ts - * embeddable.updateInput(input); - * ``` - * If you are simply rendering an embeddable, it's no problem. - * - * However when you are dealing with containers, - * you want to be sure to only pass into updateInput the actual state that changed. - * This is because calling child.updateInput({ foo }) will make foo explicit state. - * It cannot be inherited from it's parent. - * - * For example, on a dashboard, the time range is inherited by all children, - * unless they had their time range set explicitly. - * This is how "per panel time range" works. - * That action calls embeddable.updateInput({ timeRange }), - * and the time range will no longer be inherited from the container. - * - * see: https://github.com/elastic/kibana/pull/67783#discussion_r435447657 for more details. - * refer to: examples/embeddable_explorer for examples with correct usage of this component. - * - * @public - * @param props - {@link EmbeddableRendererProps} - */ -export const EmbeddableRenderer = <I extends EmbeddableInput>( - props: EmbeddableRendererProps<I> -) => { - if (isWithFactory(props)) { - return <EmbeddableByFactory {...props} />; - } - return <EmbeddableRoot embeddable={props.embeddable} input={props.input} />; -}; - -// -const EmbeddableByFactory = <I extends EmbeddableInput>({ - factory, - input, - onInputUpdated, -}: EmbeddableRendererWithFactory<I>) => { - const [embeddable, loading, error] = useEmbeddableFactory({ - factory, - input, - onInputUpdated, - }); - return <EmbeddableRoot embeddable={embeddable} loading={loading} error={error} input={input} />; -}; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx deleted file mode 100644 index 65e23b01f0b8c..0000000000000 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx +++ /dev/null @@ -1,61 +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 { HelloWorldEmbeddable, HelloWorldEmbeddableReact } from '../../tests/fixtures'; -import { EmbeddableRoot } from './embeddable_root'; -import { mount } from 'enzyme'; -import { findTestSubject } from '@elastic/eui/lib/test'; - -test('EmbeddableRoot renders an embeddable', async () => { - const embeddable = new HelloWorldEmbeddable({ id: 'hello' }); - const component = mount(<EmbeddableRoot embeddable={embeddable} />); - // Due to the way embeddables mount themselves on the dom node, they are not forced to be - // react components, and hence, we can't use the usual - // findTestSubject. - expect( - component.getDOMNode().querySelectorAll('[data-test-subj="helloWorldEmbeddable"]').length - ).toBe(1); - expect(findTestSubject(component, 'embedSpinner').length).toBe(0); - expect(findTestSubject(component, 'embedError').length).toBe(0); -}); - -test('EmbeddableRoot renders a React-based embeddable', async () => { - const embeddable = new HelloWorldEmbeddableReact({ id: 'hello' }); - const component = mount(<EmbeddableRoot embeddable={embeddable} />); - - expect(component.find('[data-test-subj="helloWorldEmbeddable"]')).toHaveLength(1); -}); - -test('EmbeddableRoot updates input', async () => { - const embeddable = new HelloWorldEmbeddable({ id: 'hello' }); - const component = mount(<EmbeddableRoot embeddable={embeddable} />); - const spy = jest.spyOn(embeddable, 'updateInput'); - const newInput = { id: 'hello', something: 'new' }; - component.setProps({ embeddable, input: newInput }); - expect(spy).toHaveBeenCalledWith(newInput); -}); - -test('EmbeddableRoot renders a spinner if loading an no embeddable given', async () => { - const component = mount(<EmbeddableRoot loading={true} />); - // Due to the way embeddables mount themselves on the dom node, they are not forced to be - // react components, and hence, we can't use the usual - // findTestSubject. - expect(findTestSubject(component, 'embedSpinner').length).toBe(1); - expect(findTestSubject(component, 'embedError').length).toBe(0); -}); - -test('EmbeddableRoot renders an error if given with no embeddable', async () => { - const component = mount(<EmbeddableRoot error="bad" />); - // Due to the way embeddables mount themselves on the dom node, they are not forced to be - // react components, and hence, we can't use the usual - // findTestSubject. - expect(findTestSubject(component, 'embedError').length).toBe(1); - expect(findTestSubject(component, 'embedSpinner').length).toBe(0); -}); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx deleted file mode 100644 index 30d795aa8b42a..0000000000000 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx +++ /dev/null @@ -1,66 +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, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; -import { EuiLoadingSpinner } from '@elastic/eui'; -import { EuiText } from '@elastic/eui'; -import { isPromise } from '@kbn/std'; -import { MaybePromise } from '@kbn/utility-types'; -import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; -import { EmbeddableErrorHandler } from './embeddable_error_handler'; - -interface Props { - embeddable?: IEmbeddable<EmbeddableInput, EmbeddableOutput, MaybePromise<ReactNode>>; - loading?: boolean; - error?: string; - input?: EmbeddableInput; -} - -export const EmbeddableRoot: React.FC<Props> = ({ embeddable, loading, error, input }) => { - const [node, setNode] = useState<ReactNode | undefined>(); - const [embeddableHasMounted, setEmbeddableHasMounted] = useState(false); - const rootRef = useRef<HTMLDivElement | null>(null); - - const updateNode = useCallback((newNode: MaybePromise<ReactNode>) => { - if (isPromise(newNode)) { - newNode.then(updateNode); - return; - } - - setNode(newNode); - }, []); - - useEffect(() => { - if (!rootRef.current || !embeddable) { - return; - } - - setEmbeddableHasMounted(true); - updateNode(embeddable.render(rootRef.current) ?? undefined); - embeddable.render(rootRef.current); - }, [updateNode, embeddable]); - - useEffect(() => { - if (input && embeddable && embeddableHasMounted) { - embeddable.updateInput(input); - } - }, [input, embeddable, embeddableHasMounted]); - - return ( - <> - <div ref={rootRef}>{node}</div> - {loading && <EuiLoadingSpinner data-test-subj="embedSpinner" />} - {error && ( - <EmbeddableErrorHandler embeddable={embeddable} error={error}> - {({ message }) => <EuiText data-test-subj="embedError">{message}</EuiText>} - </EmbeddableErrorHandler> - )} - </> - ); -}; diff --git a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx deleted file mode 100644 index 77ed3a1bc2f1d..0000000000000 --- a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx +++ /dev/null @@ -1,54 +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 { setStubKibanaServices as setPresentationPanelMocks } from '@kbn/presentation-panel-plugin/public/mocks'; -import { waitFor, render } from '@testing-library/react'; -import { ErrorEmbeddable } from './error_embeddable'; -import { EmbeddableRoot } from './embeddable_root'; - -test('ErrorEmbeddable renders an embeddable', async () => { - setPresentationPanelMocks(); - const embeddable = new ErrorEmbeddable('some error occurred', { id: '123', title: 'Error' }); - const { getByTestId, getByText } = render(<EmbeddableRoot embeddable={embeddable} />); - - expect(getByTestId('embeddableStackError')).toBeVisible(); - await waitFor(() => getByTestId('errorMessageMarkdown')); // wait for lazy markdown component - expect(getByText(/some error occurred/i)).toBeVisible(); -}); - -test('ErrorEmbeddable renders an embeddable with markdown message', async () => { - setPresentationPanelMocks(); - const error = '[some link](http://localhost:5601/takeMeThere)'; - const embeddable = new ErrorEmbeddable(error, { id: '123', title: 'Error' }); - const { getByTestId, getByText } = render(<EmbeddableRoot embeddable={embeddable} />); - - expect(getByTestId('embeddableStackError')).toBeVisible(); - await waitFor(() => getByTestId('errorMessageMarkdown')); // wait for lazy markdown component - expect(getByText(/some link/i)).toMatchInlineSnapshot(` - <a - class="euiLink emotion-euiLink-primary" - href="http://localhost:5601/takeMeThere" - rel="noopener noreferrer" - target="_blank" - > - some link - <span - class="emotion-EuiExternalLinkIcon" - data-euiicon-type="popout" - role="presentation" - /> - <span - class="emotion-euiScreenReaderOnly" - > - (external, opens in a new tab or window) - </span> - </a> - `); -}); diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts index 9e606efacaf13..92cd977d1f3d4 100644 --- a/src/plugins/embeddable/public/lib/embeddables/index.ts +++ b/src/plugins/embeddable/public/lib/embeddables/index.ts @@ -14,9 +14,6 @@ export { Embeddable } from './embeddable'; export { EmbeddableErrorHandler } from './embeddable_error_handler'; export * from './embeddable_factory'; export * from './embeddable_factory_definition'; -export { EmbeddableRenderer, useEmbeddableFactory } from './embeddable_renderer'; -export type { EmbeddableRendererProps } from './embeddable_renderer'; -export { EmbeddableRoot } from './embeddable_root'; export { ErrorEmbeddable } from './error_embeddable'; export { isErrorEmbeddable } from './is_error_embeddable'; export { isEmbeddable } from './is_embeddable'; diff --git a/src/plugins/embeddable/tsconfig.json b/src/plugins/embeddable/tsconfig.json index 161b35bc716ea..c3b5925e33233 100644 --- a/src/plugins/embeddable/tsconfig.json +++ b/src/plugins/embeddable/tsconfig.json @@ -3,19 +3,16 @@ "compilerOptions": { "outDir": "target/types" }, - "include": ["*.ts", ".storybook/**/*", "common/**/*", "public/**/*", "server/**/*"], + "include": ["*.ts", "common/**/*", "public/**/*", "server/**/*"], "kbn_references": [ "@kbn/core", "@kbn/inspector-plugin", "@kbn/saved-objects-plugin", "@kbn/kibana-utils-plugin", - "@kbn/kibana-react-plugin", "@kbn/ui-actions-plugin", "@kbn/i18n-react", - "@kbn/storybook", "@kbn/utility-types", "@kbn/es-query", - "@kbn/core-theme-browser", "@kbn/i18n", "@kbn/std", "@kbn/expressions-plugin", diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc b/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc index 25211ae2063bd..19e73f0204d6f 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc +++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc @@ -21,7 +21,8 @@ "dashboard", "dashboardEnhanced", "developerExamples", - "unifiedSearch" + "unifiedSearch", + "embeddable", ], "requiredBundles": [ "dashboardEnhanced", diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/containers/app/app.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/containers/app/app.tsx index cbfda603c8ec4..97d281d51fba6 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/containers/app/app.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/containers/app/app.tsx @@ -7,15 +7,19 @@ import React from 'react'; import { EuiPage } from '@elastic/eui'; +import { CoreStart } from '@kbn/core/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { Page } from '../../components/page'; import { DrilldownsManager } from '../drilldowns_manager'; -export const App: React.FC = () => { +export const App = ({ core }: { core: CoreStart }) => { return ( - <EuiPage> - <Page title={'UI Actions Enhanced'}> - <DrilldownsManager /> - </Page> - </EuiPage> + <KibanaRenderContextProvider i18n={core.i18n} theme={core.theme}> + <EuiPage> + <Page title={'UI Actions Enhanced'}> + <DrilldownsManager /> + </Page> + </EuiPage> + </KibanaRenderContextProvider> ); }; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx index 4ca214aa2cff4..b7a437b3d69d2 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx @@ -18,29 +18,12 @@ import { EuiFlexItem, EuiFlexGroup, } from '@elastic/eui'; -import { EmbeddableRoot, VALUE_CLICK_TRIGGER } from '@kbn/embeddable-plugin/public'; -import { SampleMlJob, SampleApp1ClickContext } from '../../triggers'; -import { ButtonEmbeddable } from '../../embeddables/button_embeddable'; +import { ReactEmbeddableRenderer, VALUE_CLICK_TRIGGER } from '@kbn/embeddable-plugin/public'; import { useUiActions } from '../../context'; - -export const job: SampleMlJob = { - job_id: '123', - job_type: 'anomaly_detector', - description: 'This is some ML job.', -}; - -export const context: SampleApp1ClickContext = { job }; +import { BUTTON_EMBEDDABLE } from '../../embeddables/register_button_embeddable'; export const DrilldownsWithEmbeddableExample: React.FC = () => { const { plugins, managerWithEmbeddable } = useUiActions(); - const embeddable = React.useMemo( - () => - new ButtonEmbeddable( - { id: 'DrilldownsWithEmbeddableExample' }, - { uiActions: plugins.uiActionsEnhanced } - ), - [plugins.uiActionsEnhanced] - ); const [showManager, setShowManager] = React.useState(false); const [openPopup, setOpenPopup] = React.useState(false); const viewRef = React.useRef<'/create' | '/manage'>('/create'); @@ -112,7 +95,13 @@ export const DrilldownsWithEmbeddableExample: React.FC = () => { <EuiFlexItem grow={false}>{openManagerButton}</EuiFlexItem> <EuiFlexItem grow={false}> <div style={{ maxWidth: 200 }}> - <EmbeddableRoot embeddable={embeddable} /> + <ReactEmbeddableRenderer<{}, {}> + type={BUTTON_EMBEDDABLE} + getParentApi={() => ({ + getSerializedStateForChild: () => undefined, + })} + hidePanelChrome={true} + /> </div> </EuiFlexItem> </EuiFlexGroup> @@ -124,7 +113,6 @@ export const DrilldownsWithEmbeddableExample: React.FC = () => { initialRoute={viewRef.current} dynamicActionManager={managerWithEmbeddable} triggers={[VALUE_CLICK_TRIGGER]} - placeContext={{ embeddable }} onClose={() => setShowManager(false)} /> </EuiFlyout> diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable.tsx new file mode 100644 index 0000000000000..f19a21d98a08c --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable.tsx @@ -0,0 +1,60 @@ +/* + * 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, { useCallback } from 'react'; +import { + DefaultEmbeddableApi, + ReactEmbeddableFactory, + VALUE_CLICK_TRIGGER, +} from '@kbn/embeddable-plugin/public'; +import { EuiCard, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import { AdvancedUiActionsStart } from '@kbn/ui-actions-enhanced-plugin/public'; +import { BUTTON_EMBEDDABLE } from './register_button_embeddable'; + +export const getButtonEmbeddableFactory = (uiActionsEnhanced: AdvancedUiActionsStart) => { + const factory: ReactEmbeddableFactory<{}, {}, DefaultEmbeddableApi<{}>> = { + type: BUTTON_EMBEDDABLE, + deserializeState: (state) => state.rawState, + buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + const api = buildApi( + { + serializeState: () => { + return { + rawState: {}, + references: [], + }; + }, + }, + {} + ); + return { + api, + Component: () => { + const onClick = useCallback(() => { + uiActionsEnhanced.getTrigger(VALUE_CLICK_TRIGGER).exec({ + embeddable: api, + data: { + data: [], + }, + }); + }, []); + return ( + <EuiFlexItem> + <EuiCard + icon={<EuiIcon size="xxl" type={`logoKibana`} />} + title={`Click me!`} + description={'This embeddable fires "VALUE_CLICK" trigger on click'} + onClick={onClick} + /> + </EuiFlexItem> + ); + }, + }; + }, + }; + return factory; +}; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts deleted file mode 100644 index df44e9061fc0e..0000000000000 --- a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts +++ /dev/null @@ -1,53 +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 { createElement } from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { AdvancedUiActionsStart } from '@kbn/ui-actions-enhanced-plugin/public'; -import { Embeddable, EmbeddableInput, VALUE_CLICK_TRIGGER } from '@kbn/embeddable-plugin/public'; -import { ButtonEmbeddableComponent } from './button_embeddable_component'; - -export const BUTTON_EMBEDDABLE = 'BUTTON_EMBEDDABLE'; - -export interface ButtonEmbeddableParams { - uiActions: AdvancedUiActionsStart; -} - -export class ButtonEmbeddable extends Embeddable { - type = BUTTON_EMBEDDABLE; - - constructor(input: EmbeddableInput, private readonly params: ButtonEmbeddableParams) { - super(input, {}); - } - - reload() {} - - private el?: HTMLElement; - - public render(el: HTMLElement): void { - super.render(el); - this.el = el; - render( - createElement(ButtonEmbeddableComponent, { - onClick: () => { - this.params.uiActions.getTrigger(VALUE_CLICK_TRIGGER).exec({ - embeddable: this, - data: { - data: [], - }, - }); - }, - }), - el - ); - } - - public destroy() { - super.destroy(); - if (this.el) unmountComponentAtNode(this.el); - } -} diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable_component.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable_component.tsx deleted file mode 100644 index 044ae3c21900d..0000000000000 --- a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable_component.tsx +++ /dev/null @@ -1,28 +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 * as React from 'react'; -import { EuiCard, EuiFlexItem, EuiIcon } from '@elastic/eui'; - -export interface ButtonEmbeddableComponentProps { - onClick: () => void; -} - -export const ButtonEmbeddableComponent: React.FC<ButtonEmbeddableComponentProps> = ({ - onClick, -}) => { - return ( - <EuiFlexItem> - <EuiCard - icon={<EuiIcon size="xxl" type={`logoKibana`} />} - title={`Click me!`} - description={'This embeddable fires "VALUE_CLICK" trigger on click'} - onClick={onClick} - /> - </EuiFlexItem> - ); -}; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/index.ts b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/index.ts deleted file mode 100644 index fb21a78c24b4a..0000000000000 --- a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export * from './button_embeddable'; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/register_button_embeddable.ts b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/register_button_embeddable.ts new file mode 100644 index 0000000000000..11ab813a85f4b --- /dev/null +++ b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/register_button_embeddable.ts @@ -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 { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; +import { StartDependencies } from '../plugin'; + +export const BUTTON_EMBEDDABLE = 'BUTTON_EMBEDDABLE'; + +export function registerButtonEmbeddable( + embeddable: EmbeddableSetup, + services: Promise<StartDependencies> +) { + embeddable.registerReactEmbeddableFactory(BUTTON_EMBEDDABLE, async () => { + const { getButtonEmbeddableFactory } = await import('./button_embeddable'); + const { uiActionsEnhanced } = await services; + return getButtonEmbeddableFactory(uiActionsEnhanced); + }); +} diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/mount.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/mount.tsx index 76708ac558fc8..636286b03efdf 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/mount.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/mount.tsx @@ -31,7 +31,7 @@ export const mount = }; const reactElement = ( <context.Provider value={deps}> - <App /> + <App core={core} /> </context.Provider> ); render(reactElement, element); diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts index fbd74fc581012..fbf06ca7c2fba 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/plugin.ts @@ -21,6 +21,7 @@ import { UiActionsEnhancedMemoryActionStorage, UiActionsEnhancedDynamicActionManager, } from '@kbn/ui-actions-enhanced-plugin/public'; +import { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; import { DashboardHelloWorldDrilldown } from './drilldowns/dashboard_hello_world_drilldown'; import { DashboardToDiscoverDrilldown } from './drilldowns/dashboard_to_discover_drilldown'; import { App1ToDashboardDrilldown } from './drilldowns/app1_to_dashboard_drilldown'; @@ -35,12 +36,14 @@ import { } from './triggers'; import { mount } from './mount'; import { App2ToDashboardDrilldown } from './drilldowns/app2_to_dashboard_drilldown'; +import { registerButtonEmbeddable } from './embeddables/register_button_embeddable'; export interface SetupDependencies { dashboard: DashboardSetup; data: DataPublicPluginSetup; developerExamples: DeveloperExamplesSetup; discover: DiscoverSetup; + embeddable: EmbeddableSetup; uiActionsEnhanced: AdvancedUiActionsSetup; } @@ -62,7 +65,7 @@ export class UiActionsEnhancedExamplesPlugin { public setup( core: CoreSetup<StartDependencies, UiActionsEnhancedExamplesStart>, - { uiActionsEnhanced: uiActions, developerExamples }: SetupDependencies + { embeddable, uiActionsEnhanced: uiActions, developerExamples }: SetupDependencies ) { const start = createStartServicesGetter(core.getStartServices); @@ -150,6 +153,12 @@ export class UiActionsEnhancedExamplesPlugin }, ], }); + + const startServicesPromise = core.getStartServices(); + registerButtonEmbeddable( + embeddable, + startServicesPromise.then(([_, startDeps]) => startDeps) + ); } public start(_core: CoreStart, plugins: StartDependencies): UiActionsEnhancedExamplesStart { diff --git a/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json b/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json index 6010bece3bd1c..666df7ab8d27a 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json +++ b/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json @@ -30,5 +30,6 @@ "@kbn/utility-types", "@kbn/presentation-publishing", "@kbn/react-kibana-mount", + "@kbn/react-kibana-context-render", ] }