diff --git a/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss b/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss index 9305927a041737e..3044a3896fa352c 100644 --- a/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss +++ b/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss @@ -29,6 +29,13 @@ .embPanel__content--fullWidth { width: 100%; } + + .embPanel__content--error { + &:hover { + box-shadow: none; + transform: none; + } + } } // HEADER diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index dfbd5f80999d6d1..a79f19cb4225c19 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -218,6 +218,7 @@ describe('HelloWorldContainer in error state', () => { test('renders a custom fatal error', () => { embeddable.triggerError(new Error('something'), true); component.update(); + component.mount(); const embeddableError = findTestSubject(component, 'embeddableError'); diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 29bb4281bec01f4..52b70f3b53406f2 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -32,6 +32,7 @@ import { } from '../embeddables/i_embeddable'; import { ViewMode } from '../types'; +import { EmbeddablePanelError } from './embeddable_panel_error'; import { RemovePanelAction } from './panel_header/panel_actions'; import { AddPanelAction } from './panel_header/panel_actions/add_panel/add_panel_action'; import { CustomizePanelTitleAction } from './panel_header/panel_actions/customize_title/customize_panel_action'; @@ -40,7 +41,7 @@ import { InspectPanelAction } from './panel_header/panel_actions/inspect_panel_a import { EditPanelAction } from '../actions'; import { CustomizePanelModal } from './panel_header/panel_actions/customize_title/customize_panel_modal'; import { EmbeddableStart } from '../../plugin'; -import { EmbeddableStateTransfer, ErrorEmbeddable, isSelfStyledEmbeddable } from '..'; +import { EmbeddableStateTransfer, isSelfStyledEmbeddable } from '..'; const sortByOrderField = ( { order: orderA }: { order?: number }, @@ -118,18 +119,10 @@ interface BasePanelActions { editPanel: EditPanelAction; } -const emptyObject = {}; -type EmptyObject = typeof emptyObject; - -type PanelUniversalActions = - | BasePanelActions - | InspectorPanelAction - | (BasePanelActions & InspectorPanelAction) - | EmptyObject; +interface PanelUniversalActions extends Partial, Partial {} export class EmbeddablePanel extends React.Component { private embeddableRoot = React.createRef(); - private errorRoot = React.createRef(); private parentSubscription?: Subscription; private subscription: Subscription = new Subscription(); private mounted: boolean = false; @@ -154,13 +147,6 @@ export class EmbeddablePanel extends React.Component { }; } - componentDidUpdate(prevProps: Props, prevState: State) { - if (this.state.error !== prevState.error) { - prevState.destroyError?.(); - this.setState({ destroyError: this.renderError() }); - } - } - private async refreshBadges() { if (!this.mounted) { return; @@ -264,24 +250,6 @@ export class EmbeddablePanel extends React.Component { } }; - private renderError() { - if (!this.state.error || !this.errorRoot.current) { - return; - } - - if (this.props.embeddable.renderError) { - return this.props.embeddable.renderError(this.errorRoot.current, this.state.error); - } - - const errorEmbeddable = new ErrorEmbeddable(this.state.error, { - id: this.props.embeddable.id, - }); - - errorEmbeddable.render(this.errorRoot.current); - - return () => errorEmbeddable.destroy(); - } - public render() { const viewOnlyMode = [ViewMode.VIEW, ViewMode.PRINT].includes(this.state.viewMode); const classes = classNames('embPanel', { @@ -330,10 +298,10 @@ export class EmbeddablePanel extends React.Component { /> )} {this.state.error && ( -
)}
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel_error.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel_error.tsx new file mode 100644 index 000000000000000..46e26fd1448bb1c --- /dev/null +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel_error.tsx @@ -0,0 +1,89 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { EuiPanel } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ErrorLike } from '@kbn/expressions-plugin/common'; +import { distinctUntilChanged, merge, of, switchMap } from 'rxjs'; +import { EditPanelAction } from '../actions'; +import { ErrorEmbeddable, IEmbeddable } from '../embeddables'; + +interface EmbeddablePanelErrorProps { + editPanelAction?: EditPanelAction; + embeddable: IEmbeddable; + error: ErrorLike; +} + +export function EmbeddablePanelError({ + editPanelAction, + embeddable, + error, +}: EmbeddablePanelErrorProps) { + const [isEditable, setEditable] = useState(false); + const ref = useRef(null); + const handleErrorClick = useMemo( + () => (isEditable ? () => editPanelAction?.execute({ embeddable }) : undefined), + [editPanelAction, embeddable, isEditable] + ); + + const title = embeddable.getTitle(); + const actionDisplayName = useMemo( + () => editPanelAction?.getDisplayName({ embeddable }), + [editPanelAction, embeddable] + ); + const ariaLabel = useMemo( + () => + !title + ? actionDisplayName + : i18n.translate('embeddableApi.panel.editPanel.displayName', { + defaultMessage: 'Edit {value}', + values: { value: title }, + }), + [title, actionDisplayName] + ); + + useEffect(() => { + const subscription = merge(embeddable.getInput$(), embeddable.getOutput$()) + .pipe( + switchMap(() => editPanelAction?.isCompatible({ embeddable }) ?? of(false)), + distinctUntilChanged() + ) + .subscribe(setEditable); + + return () => subscription.unsubscribe(); + }, [editPanelAction, embeddable]); + useEffect(() => { + if (!ref.current) { + return; + } + + if (embeddable.renderError) { + return embeddable.renderError(ref.current, error); + } + + const errorEmbeddable = new ErrorEmbeddable(error, { id: embeddable.id }); + errorEmbeddable.render(ref.current); + + return () => errorEmbeddable.destroy(); + }, [embeddable, error]); + + return ( + + ); +}