diff --git a/docs/development/core/public/kibana-plugin-core-public.scopedhistory.createhref.md b/docs/development/core/public/kibana-plugin-core-public.scopedhistory.createhref.md
index 7058656d09947..6bbab43ff6ffc 100644
--- a/docs/development/core/public/kibana-plugin-core-public.scopedhistory.createhref.md
+++ b/docs/development/core/public/kibana-plugin-core-public.scopedhistory.createhref.md
@@ -4,10 +4,12 @@
## ScopedHistory.createHref property
-Creates an href (string) to the location.
+Creates an href (string) to the location. If `prependBasePath` is true (default), it will prepend the location's path with the scoped history basePath.
Signature:
```typescript
-createHref: (location: LocationDescriptorObject) => string;
+createHref: (location: LocationDescriptorObject, { prependBasePath }?: {
+ prependBasePath?: boolean | undefined;
+ }) => string;
```
diff --git a/docs/development/core/public/kibana-plugin-core-public.scopedhistory.md b/docs/development/core/public/kibana-plugin-core-public.scopedhistory.md
index 5ea47d2090d71..fa29b32c0bafc 100644
--- a/docs/development/core/public/kibana-plugin-core-public.scopedhistory.md
+++ b/docs/development/core/public/kibana-plugin-core-public.scopedhistory.md
@@ -28,7 +28,7 @@ export declare class ScopedHistory implements Hi
| --- | --- | --- | --- |
| [action](./kibana-plugin-core-public.scopedhistory.action.md) | | Action
| The last action dispatched on the history stack. |
| [block](./kibana-plugin-core-public.scopedhistory.block.md) | | (prompt?: string | boolean | History.TransitionPromptHook<HistoryLocationState> | undefined) => UnregisterCallback
| Not supported. Use [AppMountParameters.onAppLeave](./kibana-plugin-core-public.appmountparameters.onappleave.md). |
-| [createHref](./kibana-plugin-core-public.scopedhistory.createhref.md) | | (location: LocationDescriptorObject<HistoryLocationState>) => string
| Creates an href (string) to the location. |
+| [createHref](./kibana-plugin-core-public.scopedhistory.createhref.md) | | (location: LocationDescriptorObject<HistoryLocationState>, { prependBasePath }?: {
prependBasePath?: boolean | undefined;
}) => string
| Creates an href (string) to the location. If prependBasePath
is true (default), it will prepend the location's path with the scoped history basePath. |
| [createSubHistory](./kibana-plugin-core-public.scopedhistory.createsubhistory.md) | | <SubHistoryLocationState = unknown>(basePath: string) => ScopedHistory<SubHistoryLocationState>
| Creates a ScopedHistory
for a subpath of this ScopedHistory
. Useful for applications that may have sub-apps that do not need access to the containing application's history. |
| [go](./kibana-plugin-core-public.scopedhistory.go.md) | | (n: number) => void
| Send the user forward or backwards in the history stack. |
| [goBack](./kibana-plugin-core-public.scopedhistory.goback.md) | | () => void
| Send the user one location back in the history stack. Equivalent to calling [ScopedHistory.go(-1)](./kibana-plugin-core-public.scopedhistory.go.md). If no more entries are available backwards, this is a no-op. |
diff --git a/src/core/public/application/scoped_history.test.ts b/src/core/public/application/scoped_history.test.ts
index c01eb50830516..a56cffef1e2f2 100644
--- a/src/core/public/application/scoped_history.test.ts
+++ b/src/core/public/application/scoped_history.test.ts
@@ -268,11 +268,32 @@ describe('ScopedHistory', () => {
const gh = createMemoryHistory();
gh.push('/app/wow');
const h = new ScopedHistory(gh, '/app/wow');
- expect(h.createHref({ pathname: '' })).toEqual(`/`);
+ expect(h.createHref({ pathname: '' })).toEqual(`/app/wow`);
+ expect(h.createHref({})).toEqual(`/app/wow`);
expect(h.createHref({ pathname: '/new-page', search: '?alpha=true' })).toEqual(
- `/new-page?alpha=true`
+ `/app/wow/new-page?alpha=true`
);
});
+
+ it('behave correctly with slash-ending basePath', () => {
+ const gh = createMemoryHistory();
+ gh.push('/app/wow/');
+ const h = new ScopedHistory(gh, '/app/wow/');
+ expect(h.createHref({ pathname: '' })).toEqual(`/app/wow/`);
+ expect(h.createHref({ pathname: '/new-page', search: '?alpha=true' })).toEqual(
+ `/app/wow/new-page?alpha=true`
+ );
+ });
+
+ it('skips the scoped history path when `prependBasePath` is false', () => {
+ const gh = createMemoryHistory();
+ gh.push('/app/wow');
+ const h = new ScopedHistory(gh, '/app/wow');
+ expect(h.createHref({ pathname: '' }, { prependBasePath: false })).toEqual(`/`);
+ expect(
+ h.createHref({ pathname: '/new-page', search: '?alpha=true' }, { prependBasePath: false })
+ ).toEqual(`/new-page?alpha=true`);
+ });
});
describe('action', () => {
diff --git a/src/core/public/application/scoped_history.ts b/src/core/public/application/scoped_history.ts
index c5febc7604feb..9fa8f0b7f8148 100644
--- a/src/core/public/application/scoped_history.ts
+++ b/src/core/public/application/scoped_history.ts
@@ -219,11 +219,26 @@ export class ScopedHistory
/**
* Creates an href (string) to the location.
+ * If `prependBasePath` is true (default), it will prepend the location's path with the scoped history basePath.
*
* @param location
+ * @param prependBasePath
*/
- public createHref = (location: LocationDescriptorObject): Href => {
+ public createHref = (
+ location: LocationDescriptorObject,
+ { prependBasePath = true }: { prependBasePath?: boolean } = {}
+ ): Href => {
this.verifyActive();
+ if (prependBasePath) {
+ location = this.prependBasePath(location);
+ if (location.pathname === undefined) {
+ // we always want to create an url relative to the basePath
+ // so if pathname is not present, we use the history's basePath as default
+ // we are doing that here because `prependBasePath` should not
+ // alter pathname for other method calls
+ location.pathname = this.basePath;
+ }
+ }
return this.parentHistory.createHref(location);
};
@@ -254,8 +269,7 @@ export class ScopedHistory
* Prepends the base path to string.
*/
private prependBasePathToString(path: string): string {
- path = path.startsWith('/') ? path.slice(1) : path;
- return path.length ? `${this.basePath}/${path}` : this.basePath;
+ return path.length ? `${this.basePath}/${path}`.replace(/\/{2,}/g, '/') : this.basePath;
}
/**
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index a5aa37becabc2..6d95d1bc7069c 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -1196,7 +1196,9 @@ export class ScopedHistory implements History | undefined) => UnregisterCallback;
- createHref: (location: LocationDescriptorObject) => string;
+ createHref: (location: LocationDescriptorObject, { prependBasePath }?: {
+ prependBasePath?: boolean | undefined;
+ }) => string;
createSubHistory: (basePath: string) => ScopedHistory;
go: (n: number) => void;
goBack: () => void;
diff --git a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx
index abea970749cbc..159bb54f50903 100644
--- a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx
+++ b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx
@@ -95,7 +95,7 @@ const Nav = withRouter(({ history, navigateToApp }: NavProps) => (
{
id: 'home',
name: 'Home',
- onClick: () => history.push('/'),
+ onClick: () => history.push(''),
'data-test-subj': 'fooNavHome',
},
{
diff --git a/test/plugin_functional/plugins/core_plugin_b/public/application.tsx b/test/plugin_functional/plugins/core_plugin_b/public/application.tsx
index 447307920c04c..01a63f9782563 100644
--- a/test/plugin_functional/plugins/core_plugin_b/public/application.tsx
+++ b/test/plugin_functional/plugins/core_plugin_b/public/application.tsx
@@ -102,7 +102,7 @@ const Nav = withRouter(({ history, navigateToApp }: NavProps) => (
{
id: 'home',
name: 'Home',
- onClick: () => navigateToApp('bar', { path: '/' }),
+ onClick: () => navigateToApp('bar', { path: '' }),
'data-test-subj': 'barNavHome',
},
{