Show chrome in redirect apps to improve navigation between apps (#210586)

## Summary


possibly fixes https://github.com/elastic/kibana/issues/210058

- make it `chromless:false, visibleIn: []` so that in-app redirects via
/r/ route feel smoother as chrome will stay visible
- `<EuiPageTemplate minHeight={0} offset={0}>` places loading/error in
the middle of the screen, remove scroll
- `abortController.signal` to cancel redirect in-case user has already
navigated away while waiting for shortUrl resolve (edge case)
- EuiDelayRender around "Redirecting..." spinner 
- Also do it for `forwardApp` 

### Demo (download and slowdown to see the difference): 


**before in between apps navigation** (chrome remounts for a moment,
adds junk)


https://github.com/user-attachments/assets/36d98ce9-8051-4f91-8dd9-7b406d613a73


**after in between apps navigation** (chome stays, feels smoother)


https://github.com/user-attachments/assets/4ad46954-aefe-4bde-ac4c-d598aac8b8fe


**before initial load** (chrome mounts only with discover)


https://github.com/user-attachments/assets/2618f25d-85fe-4474-8800-cbcb06db4b8e

**after initial load** (chrome mounts earlier, feels like apps loads
faster)


https://github.com/user-attachments/assets/1adc0af3-6c3c-4334-9157-fba2f008d8d5









- As a bonus, because chrome is rendered now, the error state is
friendlier, as it allows to navigate away:

![Screenshot 2025-02-11 at 15 24
29](https://github.com/user-attachments/assets/98fab62a-0ae0-4cc4-8464-c5123470ea81)
![Screenshot 2025-02-11 at 15 24
51](https://github.com/user-attachments/assets/02c455ff-9251-48f5-975c-3586c1fcbc0e)

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Anton Dosov 2025-03-03 16:08:25 +01:00 committed by GitHub
parent ed30926f0f
commit 63394e6bfd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 21 additions and 12 deletions

View file

@ -16,7 +16,6 @@ export const createLegacyUrlForwardApp = (
forwards: ForwardDefinition[]
): App => ({
id: 'kibana',
chromeless: true,
title: 'Legacy URL migration',
appRoute: '/app/kibana#/',
visibleIn: [],

View file

@ -10,7 +10,7 @@
import * as React from 'react';
import useObservable from 'react-use/lib/useObservable';
import { EuiPageTemplate } from '@elastic/eui';
import { EuiPageTemplate, EuiDelayRender } from '@elastic/eui';
import type { CustomBrandingSetup } from '@kbn/core-custom-branding-browser';
import type { ChromeDocTitle, ThemeServiceSetup } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
@ -42,7 +42,7 @@ export const Page: React.FC<PageProps> = ({
if (error) {
return (
<KibanaThemeProvider {...startServices}>
<EuiPageTemplate>
<EuiPageTemplate minHeight={0} offset={0}>
<RedirectEmptyPrompt docTitle={docTitle} error={error} homeHref={homeHref} />
</EuiPageTemplate>
</KibanaThemeProvider>
@ -51,9 +51,11 @@ export const Page: React.FC<PageProps> = ({
return (
<KibanaThemeProvider {...startServices}>
<EuiPageTemplate>
<Spinner showPlainSpinner={Boolean(hasCustomBranding)} />
</EuiPageTemplate>
<EuiDelayRender>
<EuiPageTemplate minHeight={0} offset={0}>
<Spinner showPlainSpinner={Boolean(hasCustomBranding)} />
</EuiPageTemplate>
</EuiDelayRender>
</KibanaThemeProvider>
);
};

View file

@ -35,8 +35,11 @@ export class RedirectManager {
application.register({
id: 'r',
title: 'Redirect endpoint',
chromeless: true,
visibleIn: [],
mount: async (params) => {
const abortController = new AbortController();
this.onMount(params.history.location, abortController.signal);
const { render } = await import('./render');
const [start] = await core.getStartServices();
const { chrome, uiSettings, userProfile } = start;
@ -50,9 +53,8 @@ export class RedirectManager {
homeHref: getHomeHref(http, uiSettings),
});
this.onMount(params.history.location);
return () => {
abortController.abort();
unmount();
};
},
@ -92,11 +94,11 @@ export class RedirectManager {
});
}
public onMount(location: Location) {
public onMount(location: Location, abortSignal?: AbortSignal) {
const pathname = location.pathname;
const isShortUrlRedirectBySlug = pathname.startsWith('/s/');
if (isShortUrlRedirectBySlug) {
this.navigateToShortUrlBySlug(pathname.substring('/s/'.length));
this.navigateToShortUrlBySlug(pathname.substring('/s/'.length), abortSignal);
return;
}
const urlLocationSearch = location.search;
@ -104,17 +106,23 @@ export class RedirectManager {
this.navigate(options);
}
private navigateToShortUrlBySlug(slug: string) {
private navigateToShortUrlBySlug(slug: string, abortSignal?: AbortSignal) {
(async () => {
const urlService = this.deps.url;
const shortUrls = urlService.shortUrls.get(null);
const shortUrl = await shortUrls.resolve(slug);
if (abortSignal?.aborted)
return; /* it means that the user navigated away before the short url resolved */
const locatorId = shortUrl.data.locator.id;
const locator = urlService.locators.get(locatorId);
if (!locator) throw new Error(`Locator "${locatorId}" not found.`);
const locatorState = shortUrl.data.locator.state;
await locator.navigate(locatorState, { replace: true });
})().catch((error) => {
if (abortSignal?.aborted) return;
this.error$.next(error);
// eslint-disable-next-line no-console
console.error(error);