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