-
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
Add application.navigateToUrl
core API
#67110
Changes from 2 commits
0f13ee5
07b955d
365d00b
94c5da4
89446c7
4518461
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 |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<!-- Do not edit this file. It is automatically generated by API Documenter. --> | ||
|
||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) > [navigateToUrl](./kibana-plugin-core-public.applicationstart.navigatetourl.md) | ||
|
||
## ApplicationStart.navigateToUrl() method | ||
|
||
Navigate to given url, which can either be an absolute url or a relative path, in a SPA friendly way when possible. | ||
|
||
If all these criteria are true for the given url: - (only for absolute URLs) The origin of the URL matches the origin of the browser's current location - The pathname of the URL starts with the current basePath (eg. /mybasepath/s/my-space) - The pathname segment after the basePath matches any known application route (eg. /app/<id>/ or any application's `appRoute` configuration) | ||
|
||
Then a SPA navigation will be performed using `navigateToApp` using the corresponding application and path. Else, a full page reload will be performed to navigate to the url using `window.location.assign` | ||
|
||
<b>Signature:</b> | ||
|
||
```typescript | ||
navigateToUrl(url: string): Promise<void>; | ||
``` | ||
|
||
## Parameters | ||
|
||
| Parameter | Type | Description | | ||
| --- | --- | --- | | ||
| url | <code>string</code> | an absolute url, or a relative path, to navigate to. | | ||
|
||
<b>Returns:</b> | ||
|
||
`Promise<void>` | ||
|
||
## Example | ||
|
||
|
||
```ts | ||
// current url: `https://kibana:8080/base-path/s/my-space/app/dashboard` | ||
|
||
// will call `application.navigateToApp('discover', { path: '/some-path?foo=bar'})` | ||
application.navigateToUrl('https://kibana:8080/base-path/s/my-space/app/discover/some-path?foo=bar') | ||
application.navigateToUrl('/base-path/s/my-space/app/discover/some-path?foo=bar') | ||
|
||
// will perform a full page reload using `window.location.assign` | ||
application.navigateToUrl('https://elsewhere:8080/base-path/s/my-space/app/discover/some-path') // origin does not match | ||
application.navigateToUrl('/app/discover/some-path') // does not include the current basePath | ||
application.navigateToUrl('/base-path/s/my-space/app/unknown-app/some-path') // unknown application | ||
|
||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,17 +46,14 @@ import { | |
Mounter, | ||
} from './types'; | ||
import { getLeaveAction, isConfirmAction } from './application_leave'; | ||
import { appendAppPath } from './utils'; | ||
import { appendAppPath, parseAppUrl, relativeToAbsolute } from './utils'; | ||
|
||
interface SetupDeps { | ||
context: ContextSetup; | ||
http: HttpSetup; | ||
injectedMetadata: InjectedMetadataSetup; | ||
history?: History<any>; | ||
/** | ||
* Only necessary for redirecting to legacy apps | ||
* @deprecated | ||
*/ | ||
/** Used to redirect to external urls (and legacy apps) */ | ||
redirectTo?: (path: string) => void; | ||
} | ||
|
||
|
@@ -109,6 +106,7 @@ export class ApplicationService { | |
private history?: History<any>; | ||
private mountContext?: IContextContainer<AppMountDeprecated>; | ||
private navigate?: (url: string, state: any) => void; | ||
private redirectTo?: (url: string) => void; | ||
|
||
public setup({ | ||
context, | ||
|
@@ -131,7 +129,7 @@ export class ApplicationService { | |
this.navigate = (url, state) => | ||
// basePath not needed here because `history` is configured with basename | ||
this.history ? this.history.push(url, state) : redirectTo(basePath.prepend(url)); | ||
|
||
this.redirectTo = redirectTo; | ||
this.mountContext = context.createContextContainer(); | ||
|
||
const registerStatusUpdater = (application: string, updater$: Observable<AppUpdater>) => { | ||
|
@@ -278,6 +276,20 @@ export class ApplicationService { | |
shareReplay(1) | ||
); | ||
|
||
const navigateToApp: InternalApplicationStart['navigateToApp'] = async ( | ||
appId, | ||
{ path, state }: { path?: string; state?: any } = {} | ||
) => { | ||
if (await this.shouldNavigate(overlays)) { | ||
if (path === undefined) { | ||
path = applications$.value.get(appId)?.defaultPath; | ||
} | ||
this.appLeaveHandlers.delete(this.currentAppId$.value!); | ||
this.navigate!(getAppUrl(availableMounters, appId, path), state); | ||
this.currentAppId$.next(appId); | ||
} | ||
}; | ||
|
||
return { | ||
applications$, | ||
capabilities, | ||
|
@@ -294,14 +306,13 @@ export class ApplicationService { | |
const relUrl = http.basePath.prepend(getAppUrl(availableMounters, appId, path)); | ||
return absolute ? relativeToAbsolute(relUrl) : relUrl; | ||
}, | ||
navigateToApp: async (appId, { path, state }: { path?: string; state?: any } = {}) => { | ||
if (await this.shouldNavigate(overlays)) { | ||
if (path === undefined) { | ||
path = applications$.value.get(appId)?.defaultPath; | ||
} | ||
this.appLeaveHandlers.delete(this.currentAppId$.value!); | ||
this.navigate!(getAppUrl(availableMounters, appId, path), state); | ||
this.currentAppId$.next(appId); | ||
navigateToApp, | ||
navigateToUrl: async url => { | ||
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. not 100% sure we should, but we could make a polymorphic function navigateTo(app: App): void
navigateTo(url: string): void 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. Not sure either. Upside from the polymorphic approach would mostly be reduced API exposure and 'smarter' API. Downside is a (little) less explicit API ( navigateTo(appId: string, options?: { path?: string; state?: any }): void
navigateTo(url: string): void Probably outside of the scope of this PR as it gonna impact all current calls from plugin code, but shall I open a follow up issue to discuss this possibility? |
||
const appInfo = parseAppUrl(url, http.basePath, this.apps); | ||
if (appInfo) { | ||
return navigateToApp(appInfo.app, { path: appInfo.path }); | ||
} else { | ||
return this.redirectTo!(url); | ||
} | ||
}, | ||
getComponent: () => { | ||
|
@@ -388,10 +399,3 @@ const updateStatus = <T extends AppBase>(app: T, statusUpdaters: AppUpdaterWrapp | |
...changes, | ||
}; | ||
}; | ||
|
||
function relativeToAbsolute(url: string) { | ||
// convert all link urls to absolute urls | ||
const a = document.createElement('a'); | ||
a.setAttribute('href', url); | ||
return a.href; | ||
} |
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.
Why not use a real call? The logic there is rather simple.
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.
We could remove the mock and perform a real call but as
parseAppUrl
is heavily tested in its own suite, I think current approach is slightly better in term of testing isolation.