Plugins: Show a not found error page when accessing an app for a not-found plugin (#77922)

* Plugins: Shows a not found error page when accesing an app for a non-found plugin

* Use loadingError in state to verify is a loading error
This commit is contained in:
Esteban Beltran 2023-11-09 15:33:25 +01:00 committed by GitHub
parent 494a07b522
commit 3a4f73338c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 14 additions and 3 deletions

View File

@ -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);

View File

@ -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 (
<Page navModel={navModel} pageNav={{ text: '' }} layout={currentLayout}>
{loading && <PageLoader />}
{!loading && loadingError && <EntityNotFound entity="App" />}
</Page>
);
}
@ -167,12 +170,13 @@ async function loadAppPlugin(pluginId: string, dispatch: React.Dispatch<AnyActio
}
return importAppPlugin(info);
});
dispatch(stateSlice.actions.setState({ plugin: app, loading: false, pluginNav: null }));
dispatch(stateSlice.actions.setState({ plugin: app, loading: false, loadingError: false, pluginNav: null }));
} catch (err) {
dispatch(
stateSlice.actions.setState({
plugin: null,
loading: false,
loadingError: true,
pluginNav: process.env.NODE_ENV === 'development' ? getExceptionNav(err) : getNotFoundNav(),
})
);