mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* split application utilities and associated tests to distinct files * do not match app if path does not start with the basePath * add relative paths support to `navigateToUrl` * add null-check error * update generated doc * nits on doc
This commit is contained in:
parent
412c29a997
commit
cdaa95d448
20 changed files with 649 additions and 327 deletions
|
@ -4,9 +4,11 @@
|
|||
|
||||
## ApplicationStart.getUrlForApp() method
|
||||
|
||||
Returns an URL to a given app, including the global base path. By default, the URL is relative (/basePath/app/my-app). Use the `absolute` option to generate an absolute url (http://host:port/basePath/app/my-app)
|
||||
Returns the absolute path (or URL) to a given app, including the global base path.
|
||||
|
||||
Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's location.
|
||||
By default, it returns the absolute path of the application (e.g `/basePath/app/my-app`<!-- -->). Use the `absolute` option to generate an absolute url instead (e.g `http://host:port/basePath/app/my-app`<!-- -->)
|
||||
|
||||
Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's current location.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ export interface ApplicationStart
|
|||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [getUrlForApp(appId, options)](./kibana-plugin-core-public.applicationstart.geturlforapp.md) | Returns an URL to a given app, including the global base path. By default, the URL is relative (/basePath/app/my-app). Use the <code>absolute</code> option to generate an absolute url (http://host:port/basePath/app/my-app)<!-- -->Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's location. |
|
||||
| [getUrlForApp(appId, options)](./kibana-plugin-core-public.applicationstart.geturlforapp.md) | Returns the absolute path (or URL) to a given app, including the global base path.<!-- -->By default, it returns the absolute path of the application (e.g <code>/basePath/app/my-app</code>). Use the <code>absolute</code> option to generate an absolute url instead (e.g <code>http://host:port/basePath/app/my-app</code>)<!-- -->Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's current location. |
|
||||
| [navigateToApp(appId, options)](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) | Navigate to a given app |
|
||||
| [navigateToUrl(url)](./kibana-plugin-core-public.applicationstart.navigatetourl.md) | 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 <code>appRoute</code> configuration)<!-- -->Then a SPA navigation will be performed using <code>navigateToApp</code> using the corresponding application and path. Otherwise, fallback to a full page reload to navigate to the url using <code>window.location.assign</code> |
|
||||
| [navigateToUrl(url)](./kibana-plugin-core-public.applicationstart.navigatetourl.md) | Navigate to given URL in a SPA friendly way when possible (when the URL will redirect to a valid application within the current basePath).<!-- -->The method resolves pathnames the same way browsers do when resolving a <code><a href></code> value. The provided <code>url</code> can be: - an absolute URL - an absolute path - a path relative to the current URL (window.location.href)<!-- -->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 resolved pathname of the provided URL/path 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 <code>appRoute</code> configuration)<!-- -->Then a SPA navigation will be performed using <code>navigateToApp</code> using the corresponding application and path. Otherwise, fallback to a full page reload to navigate to the url using <code>window.location.assign</code> |
|
||||
| [registerMountContext(contextName, provider)](./kibana-plugin-core-public.applicationstart.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md)<!-- -->. |
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
## 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.
|
||||
Navigate to given URL in a SPA friendly way when possible (when the URL will redirect to a valid application within the current basePath).
|
||||
|
||||
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)
|
||||
The method resolves pathnames the same way browsers do when resolving a `<a href>` value. The provided `url` can be: - an absolute URL - an absolute path - a path relative to the current URL (window.location.href)
|
||||
|
||||
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 resolved pathname of the provided URL/path 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. Otherwise, fallback to a full page reload to navigate to the url using `window.location.assign`
|
||||
|
||||
|
@ -20,7 +22,7 @@ navigateToUrl(url: string): Promise<void>;
|
|||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| url | <code>string</code> | an absolute url, or a relative path, to navigate to. |
|
||||
| url | <code>string</code> | an absolute URL, an absolute path or a relative path, to navigate to. |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
|
@ -35,11 +37,14 @@ navigateToUrl(url: string): Promise<void>;
|
|||
// 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')
|
||||
application.navigateToUrl('./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
|
||||
application.navigateToUrl('../discover') // resolve to `/base-path/s/my-space/discover` which is not a path of a known app.
|
||||
application.navigateToUrl('../../other-space/discover') // resolve to `/base-path/s/other-space/discover` which is not within the current basePath.
|
||||
|
||||
```
|
||||
|
||||
|
|
|
@ -24,6 +24,6 @@ export { mapToObject } from './map_to_object';
|
|||
export { merge } from './merge';
|
||||
export { pick } from './pick';
|
||||
export { withTimeout } from './promise';
|
||||
export { isRelativeUrl, modifyUrl, URLMeaningfulParts } from './url';
|
||||
export { isRelativeUrl, modifyUrl, getUrlOrigin, URLMeaningfulParts } from './url';
|
||||
export { unset } from './unset';
|
||||
export { getFlattenedObject } from './get_flattened_object';
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { modifyUrl, isRelativeUrl } from './url';
|
||||
import { modifyUrl, isRelativeUrl, getUrlOrigin } from './url';
|
||||
|
||||
describe('modifyUrl()', () => {
|
||||
test('throws an error with invalid input', () => {
|
||||
|
@ -83,3 +83,27 @@ describe('isRelativeUrl()', () => {
|
|||
expect(isRelativeUrl(' //evil.com')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOrigin', () => {
|
||||
describe('when passing an absolute url', () => {
|
||||
it('return origin without port when the url does not have a port', () => {
|
||||
expect(getUrlOrigin('https://example.com/file/to/path?example')).toEqual(
|
||||
'https://example.com'
|
||||
);
|
||||
});
|
||||
it('return origin with port when the url does have a port', () => {
|
||||
expect(getUrlOrigin('http://example.com:80/path/to/file')).toEqual('http://example.com:80');
|
||||
});
|
||||
});
|
||||
describe('when passing a non absolute url', () => {
|
||||
it('returns null for relative url', () => {
|
||||
expect(getUrlOrigin('./path/to/file')).toBeNull();
|
||||
});
|
||||
it('returns null for absolute path', () => {
|
||||
expect(getUrlOrigin('/path/to/file')).toBeNull();
|
||||
});
|
||||
it('returns null for empty url', () => {
|
||||
expect(getUrlOrigin('')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -125,3 +125,14 @@ export function isRelativeUrl(candidatePath: string) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the origin (protocol + host + port) from given `url` if `url` is a valid absolute url, or null otherwise
|
||||
*/
|
||||
export function getUrlOrigin(url: string): string | null {
|
||||
const obj = parseUrl(url);
|
||||
if (!obj.protocol && !obj.hostname) {
|
||||
return null;
|
||||
}
|
||||
return `${obj.protocol}//${obj.hostname}${obj.port ? `:${obj.port}` : ''}`;
|
||||
}
|
||||
|
|
|
@ -710,11 +710,17 @@ export interface ApplicationStart {
|
|||
navigateToApp(appId: string, options?: NavigateToAppOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Navigate to given url, which can either be an absolute url or a relative path, in a SPA friendly way when possible.
|
||||
* Navigate to given URL in a SPA friendly way when possible (when the URL will redirect to a valid application
|
||||
* within the current basePath).
|
||||
*
|
||||
* If all these criteria are true for the given url:
|
||||
* The method resolves pathnames the same way browsers do when resolving a `<a href>` value. The provided `url` can be:
|
||||
* - an absolute URL
|
||||
* - an absolute path
|
||||
* - a path relative to the current URL (window.location.href)
|
||||
*
|
||||
* 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 resolved pathname of the provided URL/path 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.
|
||||
|
@ -727,23 +733,27 @@ export interface ApplicationStart {
|
|||
* // 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')
|
||||
* application.navigateToUrl('./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
|
||||
* application.navigateToUrl('../discover') // resolve to `/base-path/s/my-space/discover` which is not a path of a known app.
|
||||
* application.navigateToUrl('../../other-space/discover') // resolve to `/base-path/s/other-space/discover` which is not within the current basePath.
|
||||
* ```
|
||||
*
|
||||
* @param url - an absolute url, or a relative path, to navigate to.
|
||||
* @param url - an absolute URL, an absolute path or a relative path, to navigate to.
|
||||
*/
|
||||
navigateToUrl(url: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns an URL to a given app, including the global base path.
|
||||
* By default, the URL is relative (/basePath/app/my-app).
|
||||
* Use the `absolute` option to generate an absolute url (http://host:port/basePath/app/my-app)
|
||||
* Returns the absolute path (or URL) to a given app, including the global base path.
|
||||
*
|
||||
* Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's location.
|
||||
* By default, it returns the absolute path of the application (e.g `/basePath/app/my-app`).
|
||||
* Use the `absolute` option to generate an absolute url instead (e.g `http://host:port/basePath/app/my-app`)
|
||||
*
|
||||
* Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's current location.
|
||||
*
|
||||
* @param appId
|
||||
* @param options.path - optional path inside application to deep link to
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { IBasePath } from '../http';
|
||||
import { App, AppNavLinkStatus, AppStatus, ParsedAppUrl, PublicAppInfo } from './types';
|
||||
|
||||
/**
|
||||
* Utility to remove trailing, leading or duplicate slashes.
|
||||
* By default will only remove duplicates.
|
||||
*/
|
||||
export const removeSlashes = (
|
||||
url: string,
|
||||
{
|
||||
trailing = false,
|
||||
leading = false,
|
||||
duplicates = true,
|
||||
}: { trailing?: boolean; leading?: boolean; duplicates?: boolean } = {}
|
||||
): string => {
|
||||
if (duplicates) {
|
||||
url = url.replace(/\/{2,}/g, '/');
|
||||
}
|
||||
if (trailing) {
|
||||
url = url.replace(/\/$/, '');
|
||||
}
|
||||
if (leading) {
|
||||
url = url.replace(/^\//, '');
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export const appendAppPath = (appBasePath: string, path: string = '') => {
|
||||
// Only prepend slash if not a hash or query path
|
||||
path = path === '' || path.startsWith('#') || path.startsWith('?') ? path : `/${path}`;
|
||||
// Do not remove trailing slash when in hashbang or basePath
|
||||
const removeTrailing = path.indexOf('#') === -1 && appBasePath.indexOf('#') === -1;
|
||||
return removeSlashes(`${appBasePath}${path}`, {
|
||||
trailing: removeTrailing,
|
||||
duplicates: true,
|
||||
leading: false,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a relative path to an absolute url.
|
||||
* Implementation is based on a specified behavior of the browser to automatically convert
|
||||
* a relative url to an absolute one when setting the `href` attribute of a `<a>` html element.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // current url: `https://kibana:8000/base-path/app/my-app`
|
||||
* relativeToAbsolute('/base-path/app/another-app') => `https://kibana:8000/base-path/app/another-app`
|
||||
* ```
|
||||
*/
|
||||
export const relativeToAbsolute = (url: string): string => {
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('href', url);
|
||||
return a.href;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse given url and return the associated app id and path if any app matches.
|
||||
* Input can either be:
|
||||
* - a path containing the basePath, ie `/base-path/app/my-app/some-path`
|
||||
* - an absolute url matching the `origin` of the kibana instance (as seen by the browser),
|
||||
* i.e `https://kibana:8080/base-path/app/my-app/some-path`
|
||||
*/
|
||||
export const parseAppUrl = (
|
||||
url: string,
|
||||
basePath: IBasePath,
|
||||
apps: Map<string, App<unknown>>,
|
||||
getOrigin: () => string = () => window.location.origin
|
||||
): ParsedAppUrl | undefined => {
|
||||
url = removeBasePath(url, basePath, getOrigin());
|
||||
if (!url.startsWith('/')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const app of apps.values()) {
|
||||
const appPath = app.appRoute || `/app/${app.id}`;
|
||||
|
||||
if (url.startsWith(appPath)) {
|
||||
const path = url.substr(appPath.length);
|
||||
return {
|
||||
app: app.id,
|
||||
path: path.length ? path : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const removeBasePath = (url: string, basePath: IBasePath, origin: string): string => {
|
||||
if (url.startsWith(origin)) {
|
||||
url = url.substring(origin.length);
|
||||
}
|
||||
return basePath.remove(url);
|
||||
};
|
||||
|
||||
export function getAppInfo(app: App<unknown>): PublicAppInfo {
|
||||
const navLinkStatus =
|
||||
app.navLinkStatus === AppNavLinkStatus.default
|
||||
? app.status === AppStatus.inaccessible
|
||||
? AppNavLinkStatus.hidden
|
||||
: AppNavLinkStatus.visible
|
||||
: app.navLinkStatus!;
|
||||
const { updater$, mount, ...infos } = app;
|
||||
return {
|
||||
...infos,
|
||||
status: app.status!,
|
||||
navLinkStatus,
|
||||
appRoute: app.appRoute!,
|
||||
};
|
||||
}
|
40
src/core/public/application/utils/append_app_path.test.ts
Normal file
40
src/core/public/application/utils/append_app_path.test.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { appendAppPath } from './append_app_path';
|
||||
|
||||
describe('appendAppPath', () => {
|
||||
it('appends the appBasePath with given path', () => {
|
||||
expect(appendAppPath('/app/my-app', '/some-path')).toEqual('/app/my-app/some-path');
|
||||
expect(appendAppPath('/app/my-app/', 'some-path')).toEqual('/app/my-app/some-path');
|
||||
expect(appendAppPath('/app/my-app', 'some-path')).toEqual('/app/my-app/some-path');
|
||||
expect(appendAppPath('/app/my-app', '')).toEqual('/app/my-app');
|
||||
});
|
||||
|
||||
it('preserves the trailing slash only if included in the hash or appPath', () => {
|
||||
expect(appendAppPath('/app/my-app', '/some-path/')).toEqual('/app/my-app/some-path');
|
||||
expect(appendAppPath('/app/my-app', '/some-path#/')).toEqual('/app/my-app/some-path#/');
|
||||
expect(appendAppPath('/app/my-app#/', '')).toEqual('/app/my-app#/');
|
||||
expect(appendAppPath('/app/my-app#', '/')).toEqual('/app/my-app#/');
|
||||
expect(appendAppPath('/app/my-app', '/some-path#/hash/')).toEqual(
|
||||
'/app/my-app/some-path#/hash/'
|
||||
);
|
||||
expect(appendAppPath('/app/my-app', '/some-path#/hash')).toEqual('/app/my-app/some-path#/hash');
|
||||
});
|
||||
});
|
32
src/core/public/application/utils/append_app_path.ts
Normal file
32
src/core/public/application/utils/append_app_path.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { removeSlashes } from './remove_slashes';
|
||||
|
||||
export const appendAppPath = (appBasePath: string, path: string = '') => {
|
||||
// Only prepend slash if not a hash or query path
|
||||
path = path === '' || path.startsWith('#') || path.startsWith('?') ? path : `/${path}`;
|
||||
// Do not remove trailing slash when in hashbang or basePath
|
||||
const removeTrailing = path.indexOf('#') === -1 && appBasePath.indexOf('#') === -1;
|
||||
return removeSlashes(`${appBasePath}${path}`, {
|
||||
trailing: removeTrailing,
|
||||
duplicates: true,
|
||||
leading: false,
|
||||
});
|
||||
};
|
75
src/core/public/application/utils/get_app_info.test.ts
Normal file
75
src/core/public/application/utils/get_app_info.test.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { App, AppNavLinkStatus, AppStatus } from '../types';
|
||||
import { getAppInfo } from './get_app_info';
|
||||
|
||||
describe('getAppInfo', () => {
|
||||
const createApp = (props: Partial<App> = {}): App => ({
|
||||
mount: () => () => undefined,
|
||||
updater$: of(() => undefined),
|
||||
id: 'some-id',
|
||||
title: 'some-title',
|
||||
status: AppStatus.accessible,
|
||||
navLinkStatus: AppNavLinkStatus.default,
|
||||
appRoute: `/app/some-id`,
|
||||
...props,
|
||||
});
|
||||
|
||||
it('converts an application and remove sensitive properties', () => {
|
||||
const app = createApp();
|
||||
const info = getAppInfo(app);
|
||||
|
||||
expect(info).toEqual({
|
||||
id: 'some-id',
|
||||
title: 'some-title',
|
||||
status: AppStatus.accessible,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
appRoute: `/app/some-id`,
|
||||
});
|
||||
});
|
||||
|
||||
it('computes the navLinkStatus depending on the app status', () => {
|
||||
expect(
|
||||
getAppInfo(
|
||||
createApp({
|
||||
navLinkStatus: AppNavLinkStatus.default,
|
||||
status: AppStatus.inaccessible,
|
||||
})
|
||||
)
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
})
|
||||
);
|
||||
expect(
|
||||
getAppInfo(
|
||||
createApp({
|
||||
navLinkStatus: AppNavLinkStatus.default,
|
||||
status: AppStatus.accessible,
|
||||
})
|
||||
)
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
36
src/core/public/application/utils/get_app_info.ts
Normal file
36
src/core/public/application/utils/get_app_info.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { App, AppNavLinkStatus, AppStatus, PublicAppInfo } from '../types';
|
||||
|
||||
export function getAppInfo(app: App<unknown>): PublicAppInfo {
|
||||
const navLinkStatus =
|
||||
app.navLinkStatus === AppNavLinkStatus.default
|
||||
? app.status === AppStatus.inaccessible
|
||||
? AppNavLinkStatus.hidden
|
||||
: AppNavLinkStatus.visible
|
||||
: app.navLinkStatus!;
|
||||
const { updater$, mount, ...infos } = app;
|
||||
return {
|
||||
...infos,
|
||||
status: app.status!,
|
||||
navLinkStatus,
|
||||
appRoute: app.appRoute!,
|
||||
};
|
||||
}
|
24
src/core/public/application/utils/index.ts
Normal file
24
src/core/public/application/utils/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { appendAppPath } from './append_app_path';
|
||||
export { getAppInfo } from './get_app_info';
|
||||
export { parseAppUrl } from './parse_app_url';
|
||||
export { relativeToAbsolute } from './relative_to_absolute';
|
||||
export { removeSlashes } from './remove_slashes';
|
|
@ -17,78 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { App, AppNavLinkStatus, AppStatus } from './types';
|
||||
import { BasePath } from '../http/base_path';
|
||||
import { appendAppPath, getAppInfo, parseAppUrl, relativeToAbsolute, removeSlashes } from './utils';
|
||||
|
||||
describe('removeSlashes', () => {
|
||||
it('only removes duplicates by default', () => {
|
||||
expect(removeSlashes('/some//url//to//')).toEqual('/some/url/to/');
|
||||
expect(removeSlashes('some/////other//url')).toEqual('some/other/url');
|
||||
});
|
||||
|
||||
it('remove trailing slash when `trailing` is true', () => {
|
||||
expect(removeSlashes('/some//url//to//', { trailing: true })).toEqual('/some/url/to');
|
||||
});
|
||||
|
||||
it('remove leading slash when `leading` is true', () => {
|
||||
expect(removeSlashes('/some//url//to//', { leading: true })).toEqual('some/url/to/');
|
||||
});
|
||||
|
||||
it('does not removes duplicates when `duplicates` is false', () => {
|
||||
expect(removeSlashes('/some//url//to/', { leading: true, duplicates: false })).toEqual(
|
||||
'some//url//to/'
|
||||
);
|
||||
expect(removeSlashes('/some//url//to/', { trailing: true, duplicates: false })).toEqual(
|
||||
'/some//url//to'
|
||||
);
|
||||
});
|
||||
|
||||
it('accept mixed options', () => {
|
||||
expect(
|
||||
removeSlashes('/some//url//to/', { leading: true, duplicates: false, trailing: true })
|
||||
).toEqual('some//url//to');
|
||||
expect(
|
||||
removeSlashes('/some//url//to/', { leading: true, duplicates: true, trailing: true })
|
||||
).toEqual('some/url/to');
|
||||
});
|
||||
});
|
||||
|
||||
describe('appendAppPath', () => {
|
||||
it('appends the appBasePath with given path', () => {
|
||||
expect(appendAppPath('/app/my-app', '/some-path')).toEqual('/app/my-app/some-path');
|
||||
expect(appendAppPath('/app/my-app/', 'some-path')).toEqual('/app/my-app/some-path');
|
||||
expect(appendAppPath('/app/my-app', 'some-path')).toEqual('/app/my-app/some-path');
|
||||
expect(appendAppPath('/app/my-app', '')).toEqual('/app/my-app');
|
||||
});
|
||||
|
||||
it('preserves the trailing slash only if included in the hash or appPath', () => {
|
||||
expect(appendAppPath('/app/my-app', '/some-path/')).toEqual('/app/my-app/some-path');
|
||||
expect(appendAppPath('/app/my-app', '/some-path#/')).toEqual('/app/my-app/some-path#/');
|
||||
expect(appendAppPath('/app/my-app#/', '')).toEqual('/app/my-app#/');
|
||||
expect(appendAppPath('/app/my-app#', '/')).toEqual('/app/my-app#/');
|
||||
expect(appendAppPath('/app/my-app', '/some-path#/hash/')).toEqual(
|
||||
'/app/my-app/some-path#/hash/'
|
||||
);
|
||||
expect(appendAppPath('/app/my-app', '/some-path#/hash')).toEqual('/app/my-app/some-path#/hash');
|
||||
});
|
||||
});
|
||||
|
||||
describe('relativeToAbsolute', () => {
|
||||
it('converts a relative path to an absolute url', () => {
|
||||
const origin = window.location.origin;
|
||||
expect(relativeToAbsolute('path')).toEqual(`${origin}/path`);
|
||||
expect(relativeToAbsolute('/path#hash')).toEqual(`${origin}/path#hash`);
|
||||
expect(relativeToAbsolute('/path?query=foo')).toEqual(`${origin}/path?query=foo`);
|
||||
});
|
||||
});
|
||||
import { App } from '../types';
|
||||
import { BasePath } from '../../http/base_path';
|
||||
import { parseAppUrl } from './parse_app_url';
|
||||
|
||||
describe('parseAppUrl', () => {
|
||||
let apps: Map<string, App<any>>;
|
||||
let basePath: BasePath;
|
||||
|
||||
const getOrigin = () => 'https://kibana.local:8080';
|
||||
const currentUrl =
|
||||
'https://kibana.local:8080/base-path/app/current/current-path?current-query=true';
|
||||
|
||||
const createApp = (props: Partial<App>): App => {
|
||||
const app: App = {
|
||||
|
@ -114,101 +52,178 @@ describe('parseAppUrl', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('with relative paths', () => {
|
||||
describe('with absolute paths', () => {
|
||||
it('parses the app id', () => {
|
||||
expect(parseAppUrl('/base-path/app/foo', basePath, apps, getOrigin)).toEqual({
|
||||
expect(parseAppUrl('/base-path/app/foo', basePath, apps, currentUrl)).toEqual({
|
||||
app: 'foo',
|
||||
path: undefined,
|
||||
});
|
||||
expect(parseAppUrl('/base-path/custom-bar', basePath, apps, getOrigin)).toEqual({
|
||||
expect(parseAppUrl('/base-path/custom-bar', basePath, apps, currentUrl)).toEqual({
|
||||
app: 'bar',
|
||||
path: undefined,
|
||||
});
|
||||
});
|
||||
it('parses the path', () => {
|
||||
expect(parseAppUrl('/base-path/app/foo/some/path', basePath, apps, getOrigin)).toEqual({
|
||||
expect(parseAppUrl('/base-path/app/foo/some/path', basePath, apps, currentUrl)).toEqual({
|
||||
app: 'foo',
|
||||
path: '/some/path',
|
||||
});
|
||||
expect(parseAppUrl('/base-path/custom-bar/another/path/', basePath, apps, getOrigin)).toEqual(
|
||||
{
|
||||
app: 'bar',
|
||||
path: '/another/path/',
|
||||
}
|
||||
);
|
||||
expect(
|
||||
parseAppUrl('/base-path/custom-bar/another/path/', basePath, apps, currentUrl)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
path: '/another/path/',
|
||||
});
|
||||
});
|
||||
it('includes query and hash in the path for default app route', () => {
|
||||
expect(parseAppUrl('/base-path/app/foo#hash/bang', basePath, apps, getOrigin)).toEqual({
|
||||
expect(parseAppUrl('/base-path/app/foo#hash/bang', basePath, apps, currentUrl)).toEqual({
|
||||
app: 'foo',
|
||||
path: '#hash/bang',
|
||||
});
|
||||
expect(parseAppUrl('/base-path/app/foo?hello=dolly', basePath, apps, getOrigin)).toEqual({
|
||||
expect(parseAppUrl('/base-path/app/foo?hello=dolly', basePath, apps, currentUrl)).toEqual({
|
||||
app: 'foo',
|
||||
path: '?hello=dolly',
|
||||
});
|
||||
expect(parseAppUrl('/base-path/app/foo/path?hello=dolly', basePath, apps, getOrigin)).toEqual(
|
||||
{
|
||||
app: 'foo',
|
||||
path: '/path?hello=dolly',
|
||||
}
|
||||
);
|
||||
expect(parseAppUrl('/base-path/app/foo/path#hash/bang', basePath, apps, getOrigin)).toEqual({
|
||||
expect(
|
||||
parseAppUrl('/base-path/app/foo/path?hello=dolly', basePath, apps, currentUrl)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
path: '/path?hello=dolly',
|
||||
});
|
||||
expect(parseAppUrl('/base-path/app/foo/path#hash/bang', basePath, apps, currentUrl)).toEqual({
|
||||
app: 'foo',
|
||||
path: '/path#hash/bang',
|
||||
});
|
||||
expect(
|
||||
parseAppUrl('/base-path/app/foo/path#hash/bang?hello=dolly', basePath, apps, getOrigin)
|
||||
parseAppUrl('/base-path/app/foo/path#hash/bang?hello=dolly', basePath, apps, currentUrl)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
path: '/path#hash/bang?hello=dolly',
|
||||
});
|
||||
});
|
||||
it('includes query and hash in the path for custom app route', () => {
|
||||
expect(parseAppUrl('/base-path/custom-bar#hash/bang', basePath, apps, getOrigin)).toEqual({
|
||||
expect(parseAppUrl('/base-path/custom-bar#hash/bang', basePath, apps, currentUrl)).toEqual({
|
||||
app: 'bar',
|
||||
path: '#hash/bang',
|
||||
});
|
||||
expect(parseAppUrl('/base-path/custom-bar?hello=dolly', basePath, apps, getOrigin)).toEqual({
|
||||
expect(parseAppUrl('/base-path/custom-bar?hello=dolly', basePath, apps, currentUrl)).toEqual({
|
||||
app: 'bar',
|
||||
path: '?hello=dolly',
|
||||
});
|
||||
expect(
|
||||
parseAppUrl('/base-path/custom-bar/path?hello=dolly', basePath, apps, getOrigin)
|
||||
parseAppUrl('/base-path/custom-bar/path?hello=dolly', basePath, apps, currentUrl)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
path: '/path?hello=dolly',
|
||||
});
|
||||
expect(
|
||||
parseAppUrl('/base-path/custom-bar/path#hash/bang', basePath, apps, getOrigin)
|
||||
parseAppUrl('/base-path/custom-bar/path#hash/bang', basePath, apps, currentUrl)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
path: '/path#hash/bang',
|
||||
});
|
||||
expect(
|
||||
parseAppUrl('/base-path/custom-bar/path#hash/bang?hello=dolly', basePath, apps, getOrigin)
|
||||
parseAppUrl('/base-path/custom-bar/path#hash/bang?hello=dolly', basePath, apps, currentUrl)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
path: '/path#hash/bang?hello=dolly',
|
||||
});
|
||||
});
|
||||
it('returns undefined when the app is not known', () => {
|
||||
expect(parseAppUrl('/base-path/app/non-registered', basePath, apps, getOrigin)).toEqual(
|
||||
expect(parseAppUrl('/base-path/app/non-registered', basePath, apps, currentUrl)).toEqual(
|
||||
undefined
|
||||
);
|
||||
expect(parseAppUrl('/base-path/unknown-path', basePath, apps, getOrigin)).toEqual(undefined);
|
||||
expect(parseAppUrl('/base-path/unknown-path', basePath, apps, currentUrl)).toEqual(undefined);
|
||||
});
|
||||
it('returns undefined when the path does not start with the base path', () => {
|
||||
expect(parseAppUrl('/app/foo', basePath, apps, currentUrl)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with relative paths', () => {
|
||||
it('works with sibling relative urls', () => {
|
||||
expect(
|
||||
parseAppUrl('./foo', basePath, apps, 'https://kibana.local:8080/base-path/app/current')
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
path: undefined,
|
||||
});
|
||||
});
|
||||
it('works with parent relative urls', () => {
|
||||
expect(
|
||||
parseAppUrl(
|
||||
'../custom-bar',
|
||||
basePath,
|
||||
apps,
|
||||
'https://kibana.local:8080/base-path/app/current'
|
||||
)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
path: undefined,
|
||||
});
|
||||
});
|
||||
it('works with nested parents', () => {
|
||||
expect(
|
||||
parseAppUrl(
|
||||
'../../custom-bar',
|
||||
basePath,
|
||||
apps,
|
||||
'https://kibana.local:8080/base-path/app/current/some-path'
|
||||
)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
path: undefined,
|
||||
});
|
||||
});
|
||||
it('parses the path', () => {
|
||||
expect(
|
||||
parseAppUrl(
|
||||
'./foo/path?hello=dolly',
|
||||
basePath,
|
||||
apps,
|
||||
'https://kibana.local:8080/base-path/app/current'
|
||||
)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
path: '/path?hello=dolly',
|
||||
});
|
||||
});
|
||||
it('parses the path with query and hash', () => {
|
||||
expect(
|
||||
parseAppUrl(
|
||||
'../custom-bar/path#hash?hello=dolly',
|
||||
basePath,
|
||||
apps,
|
||||
'https://kibana.local:8080/base-path/app/current'
|
||||
)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
path: '/path#hash?hello=dolly',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns undefined if the relative path redirect outside of the basePath', () => {
|
||||
expect(
|
||||
parseAppUrl(
|
||||
'../../custom-bar',
|
||||
basePath,
|
||||
apps,
|
||||
'https://kibana.local:8080/base-path/app/current'
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with absolute urls', () => {
|
||||
it('parses the app id', () => {
|
||||
expect(
|
||||
parseAppUrl('https://kibana.local:8080/base-path/app/foo', basePath, apps, getOrigin)
|
||||
parseAppUrl('https://kibana.local:8080/base-path/app/foo', basePath, apps, currentUrl)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
path: undefined,
|
||||
});
|
||||
expect(
|
||||
parseAppUrl('https://kibana.local:8080/base-path/custom-bar', basePath, apps, getOrigin)
|
||||
parseAppUrl('https://kibana.local:8080/base-path/custom-bar', basePath, apps, currentUrl)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
path: undefined,
|
||||
|
@ -220,7 +235,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/app/foo/some/path',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
|
@ -231,7 +246,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/custom-bar/another/path/',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
|
@ -244,7 +259,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/app/foo#hash/bang',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
|
@ -255,7 +270,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/app/foo?hello=dolly',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
|
@ -266,7 +281,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/app/foo/path?hello=dolly',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
|
@ -277,7 +292,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/app/foo/path#hash/bang',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
|
@ -288,7 +303,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/app/foo/path#hash/bang?hello=dolly',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'foo',
|
||||
|
@ -301,7 +316,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/custom-bar#hash/bang',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
|
@ -312,7 +327,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/custom-bar?hello=dolly',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
|
@ -323,7 +338,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/custom-bar/path?hello=dolly',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
|
@ -334,7 +349,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/custom-bar/path#hash/bang',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
|
@ -345,7 +360,7 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/custom-bar/path#hash/bang?hello=dolly',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual({
|
||||
app: 'bar',
|
||||
|
@ -358,11 +373,11 @@ describe('parseAppUrl', () => {
|
|||
'https://kibana.local:8080/base-path/app/non-registered',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual(undefined);
|
||||
expect(
|
||||
parseAppUrl('https://kibana.local:8080/base-path/unknown-path', basePath, apps, getOrigin)
|
||||
parseAppUrl('https://kibana.local:8080/base-path/unknown-path', basePath, apps, currentUrl)
|
||||
).toEqual(undefined);
|
||||
});
|
||||
it('returns undefined when origin does not match', () => {
|
||||
|
@ -371,7 +386,7 @@ describe('parseAppUrl', () => {
|
|||
'https://other-kibana.external:8080/base-path/app/foo',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual(undefined);
|
||||
expect(
|
||||
|
@ -379,62 +394,14 @@ describe('parseAppUrl', () => {
|
|||
'https://other-kibana.external:8080/base-path/custom-bar',
|
||||
basePath,
|
||||
apps,
|
||||
getOrigin
|
||||
currentUrl
|
||||
)
|
||||
).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAppInfo', () => {
|
||||
const createApp = (props: Partial<App> = {}): App => ({
|
||||
mount: () => () => undefined,
|
||||
updater$: of(() => undefined),
|
||||
id: 'some-id',
|
||||
title: 'some-title',
|
||||
status: AppStatus.accessible,
|
||||
navLinkStatus: AppNavLinkStatus.default,
|
||||
appRoute: `/app/some-id`,
|
||||
...props,
|
||||
});
|
||||
|
||||
it('converts an application and remove sensitive properties', () => {
|
||||
const app = createApp();
|
||||
const info = getAppInfo(app);
|
||||
|
||||
expect(info).toEqual({
|
||||
id: 'some-id',
|
||||
title: 'some-title',
|
||||
status: AppStatus.accessible,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
appRoute: `/app/some-id`,
|
||||
it('returns undefined when the path does not contain the base path', () => {
|
||||
expect(parseAppUrl('https://kibana.local:8080/app/foo', basePath, apps, currentUrl)).toEqual(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('computes the navLinkStatus depending on the app status', () => {
|
||||
expect(
|
||||
getAppInfo(
|
||||
createApp({
|
||||
navLinkStatus: AppNavLinkStatus.default,
|
||||
status: AppStatus.inaccessible,
|
||||
})
|
||||
)
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
})
|
||||
);
|
||||
expect(
|
||||
getAppInfo(
|
||||
createApp({
|
||||
navLinkStatus: AppNavLinkStatus.default,
|
||||
status: AppStatus.accessible,
|
||||
})
|
||||
)
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
83
src/core/public/application/utils/parse_app_url.ts
Normal file
83
src/core/public/application/utils/parse_app_url.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { getUrlOrigin } from '@kbn/std';
|
||||
import { resolve } from 'url';
|
||||
import { IBasePath } from '../../http';
|
||||
import { App, ParsedAppUrl } from '../types';
|
||||
|
||||
/**
|
||||
* Parse given URL and return the associated app id and path if any app matches, or undefined if none do.
|
||||
* Input can either be:
|
||||
*
|
||||
* - an absolute path containing the basePath,
|
||||
* e.g `/base-path/app/my-app/some-path`
|
||||
*
|
||||
* - an absolute URL matching the `origin` of the Kibana instance (as seen by the browser),
|
||||
* e.g `https://kibana:8080/base-path/app/my-app/some-path`
|
||||
*
|
||||
* - a path relative to the provided `currentUrl`.
|
||||
* e.g with `currentUrl` being `https://kibana:8080/base-path/app/current-app/some-path`
|
||||
* `../other-app/other-path` will be converted to `/base-path/app/other-app/other-path`
|
||||
*/
|
||||
export const parseAppUrl = (
|
||||
url: string,
|
||||
basePath: IBasePath,
|
||||
apps: Map<string, App<unknown>>,
|
||||
currentUrl: string = window.location.href
|
||||
): ParsedAppUrl | undefined => {
|
||||
const currentOrigin = getUrlOrigin(currentUrl);
|
||||
if (!currentOrigin) {
|
||||
throw new Error('when manually provided, currentUrl must be valid url with an origin');
|
||||
}
|
||||
const currentPath = currentUrl.substring(currentOrigin.length);
|
||||
|
||||
// remove the origin from the given url
|
||||
if (url.startsWith(currentOrigin)) {
|
||||
url = url.substring(currentOrigin.length);
|
||||
}
|
||||
|
||||
// if the path is relative (i.e `../../to/somewhere`), we convert it to absolute
|
||||
if (!url.startsWith('/')) {
|
||||
url = resolve(currentPath, url);
|
||||
}
|
||||
|
||||
// if using a basePath and the absolute path does not starts with it, it can't be a match
|
||||
const basePathValue = basePath.get();
|
||||
if (basePathValue && !url.startsWith(basePathValue)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
url = basePath.remove(url);
|
||||
if (!url.startsWith('/')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const app of apps.values()) {
|
||||
const appPath = app.appRoute || `/app/${app.id}`;
|
||||
|
||||
if (url.startsWith(appPath)) {
|
||||
const path = url.substr(appPath.length);
|
||||
return {
|
||||
app: app.id,
|
||||
path: path.length ? path : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { relativeToAbsolute } from './relative_to_absolute';
|
||||
|
||||
describe('relativeToAbsolute', () => {
|
||||
it('converts a relative path to an absolute url', () => {
|
||||
const origin = window.location.origin;
|
||||
expect(relativeToAbsolute('path')).toEqual(`${origin}/path`);
|
||||
expect(relativeToAbsolute('/path#hash')).toEqual(`${origin}/path#hash`);
|
||||
expect(relativeToAbsolute('/path?query=foo')).toEqual(`${origin}/path?query=foo`);
|
||||
});
|
||||
});
|
35
src/core/public/application/utils/relative_to_absolute.ts
Normal file
35
src/core/public/application/utils/relative_to_absolute.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts a relative path to an absolute url.
|
||||
* Implementation is based on a specified behavior of the browser to automatically convert
|
||||
* a relative url to an absolute one when setting the `href` attribute of a `<a>` html element.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // current url: `https://kibana:8000/base-path/app/my-app`
|
||||
* relativeToAbsolute('/base-path/app/another-app') => `https://kibana:8000/base-path/app/another-app`
|
||||
* ```
|
||||
*/
|
||||
export const relativeToAbsolute = (url: string): string => {
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('href', url);
|
||||
return a.href;
|
||||
};
|
53
src/core/public/application/utils/remove_slashes.test.ts
Normal file
53
src/core/public/application/utils/remove_slashes.test.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { removeSlashes } from './remove_slashes';
|
||||
|
||||
describe('removeSlashes', () => {
|
||||
it('only removes duplicates by default', () => {
|
||||
expect(removeSlashes('/some//url//to//')).toEqual('/some/url/to/');
|
||||
expect(removeSlashes('some/////other//url')).toEqual('some/other/url');
|
||||
});
|
||||
|
||||
it('remove trailing slash when `trailing` is true', () => {
|
||||
expect(removeSlashes('/some//url//to//', { trailing: true })).toEqual('/some/url/to');
|
||||
});
|
||||
|
||||
it('remove leading slash when `leading` is true', () => {
|
||||
expect(removeSlashes('/some//url//to//', { leading: true })).toEqual('some/url/to/');
|
||||
});
|
||||
|
||||
it('does not removes duplicates when `duplicates` is false', () => {
|
||||
expect(removeSlashes('/some//url//to/', { leading: true, duplicates: false })).toEqual(
|
||||
'some//url//to/'
|
||||
);
|
||||
expect(removeSlashes('/some//url//to/', { trailing: true, duplicates: false })).toEqual(
|
||||
'/some//url//to'
|
||||
);
|
||||
});
|
||||
|
||||
it('accept mixed options', () => {
|
||||
expect(
|
||||
removeSlashes('/some//url//to/', { leading: true, duplicates: false, trailing: true })
|
||||
).toEqual('some//url//to');
|
||||
expect(
|
||||
removeSlashes('/some//url//to/', { leading: true, duplicates: true, trailing: true })
|
||||
).toEqual('some/url/to');
|
||||
});
|
||||
});
|
42
src/core/public/application/utils/remove_slashes.ts
Normal file
42
src/core/public/application/utils/remove_slashes.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility to remove trailing, leading or duplicate slashes.
|
||||
* By default will only remove duplicates.
|
||||
*/
|
||||
export const removeSlashes = (
|
||||
url: string,
|
||||
{
|
||||
trailing = false,
|
||||
leading = false,
|
||||
duplicates = true,
|
||||
}: { trailing?: boolean; leading?: boolean; duplicates?: boolean } = {}
|
||||
): string => {
|
||||
if (duplicates) {
|
||||
url = url.replace(/\/{2,}/g, '/');
|
||||
}
|
||||
if (trailing) {
|
||||
url = url.replace(/\/$/, '');
|
||||
}
|
||||
if (leading) {
|
||||
url = url.replace(/^\//, '');
|
||||
}
|
||||
return url;
|
||||
};
|
|
@ -16,24 +16,6 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { modifyUrl } from '@kbn/std';
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue