diff --git a/public/app/features/plugins/components/AppRootPage.test.tsx b/public/app/features/plugins/components/AppRootPage.test.tsx index c590b46f932..69f01e9dc37 100644 --- a/public/app/features/plugins/components/AppRootPage.test.tsx +++ b/public/app/features/plugins/components/AppRootPage.test.tsx @@ -102,6 +102,13 @@ describe('AppRootPage', () => { enabled: true, }); + it("should show a not found page if the plugin settings can't load", async () => { + getPluginSettingsMock.mockRejectedValue(new Error('Unknown Plugin')); + // Renders once for the first time + await renderUnderRouter(); + expect(await screen.findByText('App not found')).toBeVisible(); + }); + it('should not render the component if we are not under a plugin path', async () => { getPluginSettingsMock.mockResolvedValue(pluginMeta); diff --git a/public/app/features/plugins/components/AppRootPage.tsx b/public/app/features/plugins/components/AppRootPage.tsx index 182c177ca1e..cf4abcd403f 100644 --- a/public/app/features/plugins/components/AppRootPage.tsx +++ b/public/app/features/plugins/components/AppRootPage.tsx @@ -8,6 +8,7 @@ import { config, locationSearchToObject } from '@grafana/runtime'; import { Alert } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; +import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { appEvents, contextSrv } from 'app/core/core'; import { getNotFoundNav, getWarningNav, getExceptionNav } from 'app/core/navigation/errorModels'; @@ -27,19 +28,20 @@ interface Props { interface State { loading: boolean; + loadingError: boolean; plugin?: AppPlugin | null; // Used to display a tab navigation (used before the new Top Nav) pluginNav: NavModel | null; } -const initialState: State = { loading: true, pluginNav: null, plugin: null }; +const initialState: State = { loading: true, loadingError: false, pluginNav: null, plugin: null }; export function AppRootPage({ pluginId, pluginNavSection }: Props) { const match = useRouteMatch(); const location = useLocation(); const [state, dispatch] = useReducer(stateSlice.reducer, initialState); const currentUrl = config.appSubUrl + location.pathname + location.search; - const { plugin, loading, pluginNav } = state; + const { plugin, loading, loadingError, pluginNav } = state; const navModel = buildPluginSectionNav(pluginNavSection, pluginNav, currentUrl); const queryParams = useMemo(() => locationSearchToObject(location.search), [location.search]); const context = useMemo(() => buildPluginPageContext(navModel), [navModel]); @@ -60,6 +62,7 @@ export function AppRootPage({ pluginId, pluginNavSection }: Props) { return ( {loading && } + {!loading && loadingError && } ); } @@ -167,12 +170,13 @@ async function loadAppPlugin(pluginId: string, dispatch: React.Dispatch