-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Dashboard] Fix alias redirect & update error handling #159742
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,8 +18,10 @@ import React, { | |
} from 'react'; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
import classNames from 'classnames'; | ||
import useUnmount from 'react-use/lib/useUnmount'; | ||
|
||
import { EuiLoadingElastic, EuiLoadingSpinner } from '@elastic/eui'; | ||
import { ErrorEmbeddable, isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; | ||
|
||
import { | ||
DashboardAPI, | ||
|
@@ -47,6 +49,7 @@ export const DashboardRenderer = forwardRef<AwaitingDashboardAPI, DashboardRende | |
const [loading, setLoading] = useState(true); | ||
const [screenshotMode, setScreenshotMode] = useState(false); | ||
const [dashboardContainer, setDashboardContainer] = useState<DashboardContainer>(); | ||
const [fatalError, setFatalError] = useState<ErrorEmbeddable | undefined>(); | ||
|
||
useImperativeHandle( | ||
ref, | ||
|
@@ -65,23 +68,22 @@ export const DashboardRenderer = forwardRef<AwaitingDashboardAPI, DashboardRende | |
})(); | ||
}, []); | ||
|
||
useEffect(() => { | ||
if (!dashboardContainer) return; | ||
|
||
// When a dashboard already exists, don't rebuild it, just set a new id. | ||
dashboardContainer.navigateToDashboard(savedObjectId); | ||
|
||
// Disabling exhaustive deps because this useEffect should only be triggered when the savedObjectId changes. | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [savedObjectId]); | ||
|
||
const id = useMemo(() => uuidv4(), []); | ||
|
||
useEffect(() => { | ||
let canceled = false; | ||
let destroyContainer: () => void; | ||
if (dashboardContainer) { | ||
// When a dashboard already exists, don't rebuild it, just set a new id. | ||
dashboardContainer.navigateToDashboard(savedObjectId); | ||
|
||
return; | ||
} | ||
setLoading(true); | ||
|
||
let canceled = false; | ||
(async () => { | ||
fatalError?.destroy(); | ||
setFatalError(undefined); | ||
|
||
const creationOptions = await getCreationOptions?.(); | ||
|
||
// Lazy loading all services is required in this component because it is exported and contributes to the bundle size. | ||
|
@@ -91,33 +93,42 @@ export const DashboardRenderer = forwardRef<AwaitingDashboardAPI, DashboardRende | |
const dashboardFactory = embeddable.getEmbeddableFactory( | ||
DASHBOARD_CONTAINER_TYPE | ||
) as DashboardContainerFactory & { create: DashboardContainerFactoryDefinition['create'] }; | ||
const container = (await dashboardFactory?.create( | ||
const container = await dashboardFactory?.create( | ||
{ id } as unknown as DashboardContainerInput, // Input from creationOptions is used instead. | ||
undefined, | ||
creationOptions, | ||
savedObjectId | ||
)) as DashboardContainer; | ||
); | ||
|
||
if (canceled) { | ||
container.destroy(); | ||
return; | ||
} | ||
|
||
setLoading(false); | ||
|
||
if (isErrorEmbeddable(container)) { | ||
setFatalError(container); | ||
return; | ||
} | ||
|
||
if (dashboardRoot.current) { | ||
container.render(dashboardRoot.current); | ||
} | ||
|
||
setDashboardContainer(container); | ||
destroyContainer = () => container.destroy(); | ||
})(); | ||
return () => { | ||
canceled = true; | ||
destroyContainer?.(); | ||
}; | ||
// Disabling exhaustive deps because embeddable should only be created on first render. | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
}, [savedObjectId]); | ||
|
||
useUnmount(() => { | ||
fatalError?.destroy(); | ||
dashboardContainer?.destroy(); | ||
}); | ||
|
||
const viewportClasses = classNames( | ||
'dashboardViewport', | ||
|
@@ -133,7 +144,9 @@ export const DashboardRenderer = forwardRef<AwaitingDashboardAPI, DashboardRende | |
|
||
return ( | ||
<div className={viewportClasses}> | ||
{loading ? loadingSpinner : <div ref={dashboardRoot} />} | ||
{loading && loadingSpinner} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit, there should never be a case where loadingSpinner and fatalError.render() are displayed but there could be if both loading and fatalError are set. Would it be clearer to move this logic to a function and use early returns so only a single item is rendered. For example
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's true. Even if it wouldn't necessarily happen, it's still much cleaner and more understandable to do it this way. |
||
{fatalError && fatalError.render()} | ||
{!fatalError && !loading && <div ref={dashboardRoot} />} | ||
</div> | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't
setLoading(false);
get called if container is an error embeddable? The container finished loading, even if it loaded in an error stateThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, I didn't catch that because the error was rendered if it was present no matter if the loading state was true or not. I've straightened that out!