mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[7.x] [new-platform] Add legacy
property to NavLinks registe… (#42220)
This commit is contained in:
parent
3b622e6a07
commit
0b52c2672a
29 changed files with 246 additions and 170 deletions
|
@ -4,8 +4,10 @@
|
|||
|
||||
## ApplicationStart.availableApps property
|
||||
|
||||
Apps available based on the current capabilities. Should be used to show navigation links and make routing decisions.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
availableApps: CapabilitiesStart['availableApps'];
|
||||
availableApps: readonly App[];
|
||||
```
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
## ApplicationStart.capabilities property
|
||||
|
||||
Gets the read-only capabilities.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
capabilities: CapabilitiesStart['capabilities'];
|
||||
capabilities: RecursiveReadonly<Capabilities>;
|
||||
```
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
## ApplicationStart interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
|
@ -14,7 +15,6 @@ export interface ApplicationStart
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [availableApps](./kibana-plugin-public.applicationstart.availableapps.md) | <code>CapabilitiesStart['availableApps']</code> | |
|
||||
| [capabilities](./kibana-plugin-public.applicationstart.capabilities.md) | <code>CapabilitiesStart['capabilities']</code> | |
|
||||
| [mount](./kibana-plugin-public.applicationstart.mount.md) | <code>(mountHandler: Function) => void</code> | |
|
||||
| [availableApps](./kibana-plugin-public.applicationstart.availableapps.md) | <code>readonly App[]</code> | Apps available based on the current capabilities. Should be used to show navigation links and make routing decisions. |
|
||||
| [capabilities](./kibana-plugin-public.applicationstart.capabilities.md) | <code>RecursiveReadonly<Capabilities></code> | Gets the read-only capabilities. |
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationStart](./kibana-plugin-public.applicationstart.md) > [mount](./kibana-plugin-public.applicationstart.mount.md)
|
||||
|
||||
## ApplicationStart.mount property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
mount: (mountHandler: Function) => void;
|
||||
```
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
## ChromeNavLink.active property
|
||||
|
||||
Indicates whether or not this app is currently on the screen.
|
||||
> Warning: This API is now obsolete.
|
||||
>
|
||||
>
|
||||
|
||||
NOTE: remove this when ApplicationService is implemented and managing apps.
|
||||
Indicates whether or not this app is currently on the screen.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
## ChromeNavLink.disabled property
|
||||
|
||||
Disables a link from being clickable.
|
||||
> Warning: This API is now obsolete.
|
||||
>
|
||||
>
|
||||
|
||||
NOTE: this is only used by the ML and Graph plugins currently. They use this field to disable the nav link when the license is expired.
|
||||
Disables a link from being clickable.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
Hides a link from the navigation.
|
||||
|
||||
NOTE: remove this when ApplicationService is implemented. Instead, plugins should only register an Application if needed.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
## ChromeNavLink.linkToLastSubUrl property
|
||||
|
||||
Whether or not the subUrl feature should be enabled.
|
||||
> Warning: This API is now obsolete.
|
||||
>
|
||||
>
|
||||
|
||||
NOTE: only read by legacy platform.
|
||||
Whether or not the subUrl feature should be enabled.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -15,17 +15,17 @@ export interface ChromeNavLink
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [active](./kibana-plugin-public.chromenavlink.active.md) | <code>boolean</code> | Indicates whether or not this app is currently on the screen.<!-- -->NOTE: remove this when ApplicationService is implemented and managing apps. |
|
||||
| [active](./kibana-plugin-public.chromenavlink.active.md) | <code>boolean</code> | Indicates whether or not this app is currently on the screen. |
|
||||
| [baseUrl](./kibana-plugin-public.chromenavlink.baseurl.md) | <code>string</code> | The base route used to open the root of an application. |
|
||||
| [disabled](./kibana-plugin-public.chromenavlink.disabled.md) | <code>boolean</code> | Disables a link from being clickable.<!-- -->NOTE: this is only used by the ML and Graph plugins currently. They use this field to disable the nav link when the license is expired. |
|
||||
| [disabled](./kibana-plugin-public.chromenavlink.disabled.md) | <code>boolean</code> | Disables a link from being clickable. |
|
||||
| [euiIconType](./kibana-plugin-public.chromenavlink.euiicontype.md) | <code>string</code> | A EUI iconType that will be used for the app's icon. This icon takes precendence over the <code>icon</code> property. |
|
||||
| [hidden](./kibana-plugin-public.chromenavlink.hidden.md) | <code>boolean</code> | Hides a link from the navigation.<!-- -->NOTE: remove this when ApplicationService is implemented. Instead, plugins should only register an Application if needed. |
|
||||
| [hidden](./kibana-plugin-public.chromenavlink.hidden.md) | <code>boolean</code> | Hides a link from the navigation. |
|
||||
| [icon](./kibana-plugin-public.chromenavlink.icon.md) | <code>string</code> | A URL to an image file used as an icon. Used as a fallback if <code>euiIconType</code> is not provided. |
|
||||
| [id](./kibana-plugin-public.chromenavlink.id.md) | <code>string</code> | A unique identifier for looking up links. |
|
||||
| [linkToLastSubUrl](./kibana-plugin-public.chromenavlink.linktolastsuburl.md) | <code>boolean</code> | Whether or not the subUrl feature should be enabled.<!-- -->NOTE: only read by legacy platform. |
|
||||
| [linkToLastSubUrl](./kibana-plugin-public.chromenavlink.linktolastsuburl.md) | <code>boolean</code> | Whether or not the subUrl feature should be enabled. |
|
||||
| [order](./kibana-plugin-public.chromenavlink.order.md) | <code>number</code> | An ordinal used to sort nav links relative to one another for display. |
|
||||
| [subUrlBase](./kibana-plugin-public.chromenavlink.suburlbase.md) | <code>string</code> | A url base that legacy apps can set to match deep URLs to an applcation.<!-- -->NOTE: this should be removed once legacy apps are gone. |
|
||||
| [subUrlBase](./kibana-plugin-public.chromenavlink.suburlbase.md) | <code>string</code> | A url base that legacy apps can set to match deep URLs to an applcation. |
|
||||
| [title](./kibana-plugin-public.chromenavlink.title.md) | <code>string</code> | The title of the application. |
|
||||
| [tooltip](./kibana-plugin-public.chromenavlink.tooltip.md) | <code>string</code> | A tooltip shown when hovering over an app link. |
|
||||
| [url](./kibana-plugin-public.chromenavlink.url.md) | <code>string</code> | A url that legacy apps can set to deep link into their applications.<!-- -->NOTE: Currently used by the "lastSubUrl" feature legacy/ui/chrome. This should be removed once the ApplicationService is implemented and mounting apps. At that time, each app can handle opening to the previous location when they are mounted. |
|
||||
| [url](./kibana-plugin-public.chromenavlink.url.md) | <code>string</code> | A url that legacy apps can set to deep link into their applications. |
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
## ChromeNavLink.subUrlBase property
|
||||
|
||||
A url base that legacy apps can set to match deep URLs to an applcation.
|
||||
> Warning: This API is now obsolete.
|
||||
>
|
||||
>
|
||||
|
||||
NOTE: this should be removed once legacy apps are gone.
|
||||
A url base that legacy apps can set to match deep URLs to an applcation.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
## ChromeNavLink.url property
|
||||
|
||||
A url that legacy apps can set to deep link into their applications.
|
||||
> Warning: This API is now obsolete.
|
||||
>
|
||||
>
|
||||
|
||||
NOTE: Currently used by the "lastSubUrl" feature legacy/ui/chrome. This should be removed once the ApplicationService is implemented and mounting apps. At that time, each app can handle opening to the previous location when they are mounted.
|
||||
A url that legacy apps can set to deep link into their applications.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -16,5 +16,5 @@ export interface OverlayStart
|
|||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | <code>(flyoutChildren: React.ReactNode, flyoutProps?: {</code><br/><code> closeButtonAriaLabel?: string;</code><br/><code> 'data-test-subj'?: string;</code><br/><code> }) => OverlayRef</code> | |
|
||||
| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | <code>(modalChildren: React.ReactNode, modalProps?: {</code><br/><code> closeButtonAriaLabel?: string;</code><br/><code> 'data-test-subj'?: string;</code><br/><code> }) => OverlayRef</code> | |
|
||||
| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | <code>(modalChildren: React.ReactNode, modalProps?: {</code><br/><code> className?: string;</code><br/><code> closeButtonAriaLabel?: string;</code><br/><code> 'data-test-subj'?: string;</code><br/><code> }) => OverlayRef</code> | |
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
```typescript
|
||||
openModal: (modalChildren: React.ReactNode, modalProps?: {
|
||||
className?: string;
|
||||
closeButtonAriaLabel?: string;
|
||||
'data-test-subj'?: string;
|
||||
}) => OverlayRef;
|
||||
|
|
|
@ -28,7 +28,6 @@ const createSetupContractMock = (): jest.Mocked<ApplicationSetup> => ({
|
|||
});
|
||||
|
||||
const createStartContractMock = (): jest.Mocked<ApplicationStart> => ({
|
||||
mount: jest.fn(),
|
||||
...capabilitiesServiceMock.createStartContract(),
|
||||
});
|
||||
|
||||
|
|
|
@ -28,11 +28,16 @@ describe('#start()', () => {
|
|||
setup.registerApp({ id: 'app1' } as any);
|
||||
setup.registerLegacyApp({ id: 'app2' } as any);
|
||||
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
|
||||
expect((await service.start({ injectedMetadata })).availableApps).toMatchInlineSnapshot(`
|
||||
const startContract = await service.start({ injectedMetadata });
|
||||
expect(startContract.availableApps).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "app1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(startContract.availableLegacyApps).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "app2",
|
||||
},
|
||||
|
@ -48,6 +53,7 @@ Array [
|
|||
await service.start({ injectedMetadata });
|
||||
expect(MockCapabilitiesService.start).toHaveBeenCalledWith({
|
||||
apps: [{ id: 'app1' }],
|
||||
legacyApps: [],
|
||||
injectedMetadata,
|
||||
});
|
||||
});
|
||||
|
@ -59,7 +65,8 @@ Array [
|
|||
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
|
||||
await service.start({ injectedMetadata });
|
||||
expect(MockCapabilitiesService.start).toHaveBeenCalledWith({
|
||||
apps: [{ id: 'legacyApp1' }],
|
||||
apps: [],
|
||||
legacyApps: [{ id: 'legacyApp1' }],
|
||||
injectedMetadata,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
*/
|
||||
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
import { CapabilitiesStart, CapabilitiesService, Capabilities } from './capabilities';
|
||||
import { CapabilitiesService, Capabilities } from './capabilities';
|
||||
import { InjectedMetadataStart } from '../injected_metadata';
|
||||
import { RecursiveReadonly } from '../../utils';
|
||||
|
||||
interface BaseApp {
|
||||
id: string;
|
||||
|
@ -59,11 +60,6 @@ interface BaseApp {
|
|||
|
||||
/** @public */
|
||||
export interface App extends BaseApp {
|
||||
/**
|
||||
* The root route to mount this application at.
|
||||
*/
|
||||
rootRoute: string;
|
||||
|
||||
/**
|
||||
* A mount function called when the user navigates to this app's `rootRoute`.
|
||||
* @param targetDomElement An HTMLElement to mount the application onto.
|
||||
|
@ -98,10 +94,27 @@ export interface ApplicationSetup {
|
|||
registerLegacyApp(app: LegacyApp): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ApplicationStart {
|
||||
mount: (mountHandler: Function) => void;
|
||||
availableApps: CapabilitiesStart['availableApps'];
|
||||
capabilities: CapabilitiesStart['capabilities'];
|
||||
/**
|
||||
* Gets the read-only capabilities.
|
||||
*/
|
||||
capabilities: RecursiveReadonly<Capabilities>;
|
||||
|
||||
/**
|
||||
* Apps available based on the current capabilities. Should be used
|
||||
* to show navigation links and make routing decisions.
|
||||
*/
|
||||
availableApps: readonly App[];
|
||||
|
||||
/**
|
||||
* Apps available based on the current capabilities. Should be used
|
||||
* to show navigation links and make routing decisions.
|
||||
* @internal
|
||||
*/
|
||||
availableLegacyApps: readonly LegacyApp[];
|
||||
}
|
||||
|
||||
interface StartDeps {
|
||||
|
@ -132,17 +145,11 @@ export class ApplicationService {
|
|||
this.apps$.complete();
|
||||
this.legacyApps$.complete();
|
||||
|
||||
const apps = [...this.apps$.value, ...this.legacyApps$.value];
|
||||
const { capabilities, availableApps } = await this.capabilities.start({
|
||||
apps,
|
||||
return this.capabilities.start({
|
||||
apps: this.apps$.value,
|
||||
legacyApps: this.legacyApps$.value,
|
||||
injectedMetadata,
|
||||
});
|
||||
|
||||
return {
|
||||
mount() {},
|
||||
capabilities,
|
||||
availableApps,
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
*/
|
||||
import { CapabilitiesService, CapabilitiesStart } from './capabilities_service';
|
||||
import { deepFreeze } from '../../../utils/';
|
||||
import { MixedApp } from '../application_service';
|
||||
import { App, LegacyApp } from '../application_service';
|
||||
|
||||
const createStartContractMock = (
|
||||
apps: readonly MixedApp[] = []
|
||||
apps: readonly App[] = [],
|
||||
legacyApps: readonly LegacyApp[] = []
|
||||
): jest.Mocked<CapabilitiesStart> => ({
|
||||
availableApps: apps,
|
||||
availableLegacyApps: legacyApps,
|
||||
capabilities: deepFreeze({
|
||||
catalogue: {},
|
||||
management: {},
|
||||
|
@ -33,7 +35,9 @@ const createStartContractMock = (
|
|||
|
||||
type CapabilitiesServiceContract = PublicMethodsOf<CapabilitiesService>;
|
||||
const createMock = (): jest.Mocked<CapabilitiesServiceContract> => ({
|
||||
start: jest.fn().mockImplementation(({ apps }) => createStartContractMock(apps)),
|
||||
start: jest
|
||||
.fn()
|
||||
.mockImplementation(({ apps, legacyApps }) => createStartContractMock(apps, legacyApps)),
|
||||
});
|
||||
|
||||
export const capabilitiesServiceMock = {
|
||||
|
|
|
@ -30,25 +30,33 @@ describe('#start', () => {
|
|||
navLinks: {
|
||||
app1: true,
|
||||
app2: false,
|
||||
legacyApp1: true,
|
||||
legacyApp2: false,
|
||||
},
|
||||
foo: { feature: true },
|
||||
bar: { feature: true },
|
||||
},
|
||||
} as any,
|
||||
}).start();
|
||||
|
||||
const apps = [{ id: 'app1' }, { id: 'app2', capabilities: { app2: { feature: true } } }] as any;
|
||||
const legacyApps = [
|
||||
{ id: 'legacyApp1' },
|
||||
{ id: 'legacyApp2', capabilities: { app2: { feature: true } } },
|
||||
] as any;
|
||||
|
||||
it('filters available apps based on returned navLinks', async () => {
|
||||
const service = new CapabilitiesService();
|
||||
expect((await service.start({ apps, injectedMetadata })).availableApps).toEqual([
|
||||
{ id: 'app1' },
|
||||
]);
|
||||
const startContract = await service.start({ apps, legacyApps, injectedMetadata });
|
||||
expect(startContract.availableApps).toEqual([{ id: 'app1' }]);
|
||||
expect(startContract.availableLegacyApps).toEqual([{ id: 'legacyApp1' }]);
|
||||
});
|
||||
|
||||
it('does not allow Capabilities to be modified', async () => {
|
||||
const service = new CapabilitiesService();
|
||||
const { capabilities } = await service.start({
|
||||
apps,
|
||||
legacyApps,
|
||||
injectedMetadata,
|
||||
});
|
||||
|
||||
|
|
|
@ -18,11 +18,12 @@
|
|||
*/
|
||||
|
||||
import { deepFreeze, RecursiveReadonly } from '../../../utils';
|
||||
import { MixedApp } from '../application_service';
|
||||
import { LegacyApp, App } from '../application_service';
|
||||
import { InjectedMetadataStart } from '../../injected_metadata';
|
||||
|
||||
interface StartDeps {
|
||||
apps: readonly MixedApp[];
|
||||
apps: readonly App[];
|
||||
legacyApps: readonly LegacyApp[];
|
||||
injectedMetadata: InjectedMetadataStart;
|
||||
}
|
||||
|
||||
|
@ -49,35 +50,28 @@ export interface Capabilities {
|
|||
[key: string]: Record<string, boolean | Record<string, boolean>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capabilities Setup.
|
||||
* @public
|
||||
*/
|
||||
export interface CapabilitiesStart {
|
||||
/**
|
||||
* Gets the read-only capabilities.
|
||||
*/
|
||||
capabilities: RecursiveReadonly<Capabilities>;
|
||||
|
||||
/**
|
||||
* Apps available based on the current capabilities. Should be used
|
||||
* to show navigation links and make routing decisions.
|
||||
*/
|
||||
availableApps: readonly MixedApp[];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface CapabilitiesStart {
|
||||
capabilities: RecursiveReadonly<Capabilities>;
|
||||
availableApps: readonly App[];
|
||||
availableLegacyApps: readonly LegacyApp[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Service that is responsible for UI Capabilities.
|
||||
* @internal
|
||||
*/
|
||||
export class CapabilitiesService {
|
||||
public async start({ apps, injectedMetadata }: StartDeps): Promise<CapabilitiesStart> {
|
||||
public async start({
|
||||
apps,
|
||||
legacyApps,
|
||||
injectedMetadata,
|
||||
}: StartDeps): Promise<CapabilitiesStart> {
|
||||
const capabilities = deepFreeze(injectedMetadata.getCapabilities());
|
||||
const availableApps = apps.filter(app => capabilities.navLinks[app.id]);
|
||||
|
||||
return {
|
||||
availableApps,
|
||||
availableApps: apps.filter(app => capabilities.navLinks[app.id]),
|
||||
availableLegacyApps: legacyApps.filter(app => capabilities.navLinks[app.id]),
|
||||
capabilities,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { Capabilities, CapabilitiesService, CapabilitiesStart } from './capabilities_service';
|
||||
export { Capabilities, CapabilitiesService } from './capabilities_service';
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
} from './chrome_service';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<InternalChromeStart> = {
|
||||
const startContract: DeeplyMockedKeys<InternalChromeStart> = {
|
||||
getComponent: jest.fn(),
|
||||
navLinks: {
|
||||
getNavLinks$: jest.fn(),
|
||||
|
@ -66,6 +66,7 @@ const createStartContractMock = () => {
|
|||
getHelpExtension$: jest.fn(),
|
||||
setHelpExtension: jest.fn(),
|
||||
};
|
||||
startContract.navLinks.getAll.mockReturnValue([]);
|
||||
startContract.getBrand$.mockReturnValue(new BehaviorSubject({} as ChromeBrand));
|
||||
startContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false));
|
||||
startContract.getIsCollapsed$.mockReturnValue(new BehaviorSubject(false));
|
||||
|
|
|
@ -28,29 +28,6 @@ export interface ChromeNavLink {
|
|||
*/
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* Indicates whether or not this app is currently on the screen.
|
||||
*
|
||||
* NOTE: remove this when ApplicationService is implemented and managing apps.
|
||||
*/
|
||||
readonly active?: boolean;
|
||||
|
||||
/**
|
||||
* Disables a link from being clickable.
|
||||
*
|
||||
* NOTE: this is only used by the ML and Graph plugins currently. They use this field
|
||||
* to disable the nav link when the license is expired.
|
||||
*/
|
||||
readonly disabled?: boolean;
|
||||
|
||||
/**
|
||||
* Hides a link from the navigation.
|
||||
*
|
||||
* NOTE: remove this when ApplicationService is implemented. Instead, plugins should only
|
||||
* register an Application if needed.
|
||||
*/
|
||||
readonly hidden?: boolean;
|
||||
|
||||
/**
|
||||
* An ordinal used to sort nav links relative to one another for display.
|
||||
*/
|
||||
|
@ -61,16 +38,16 @@ export interface ChromeNavLink {
|
|||
*/
|
||||
readonly title: string;
|
||||
|
||||
/**
|
||||
* A tooltip shown when hovering over an app link.
|
||||
*/
|
||||
readonly tooltip?: string;
|
||||
|
||||
/**
|
||||
* The base route used to open the root of an application.
|
||||
*/
|
||||
readonly baseUrl: string;
|
||||
|
||||
/**
|
||||
* A tooltip shown when hovering over an app link.
|
||||
*/
|
||||
readonly tooltip?: string;
|
||||
|
||||
/**
|
||||
* A EUI iconType that will be used for the app's icon. This icon
|
||||
* takes precendence over the `icon` property.
|
||||
|
@ -88,25 +65,70 @@ export interface ChromeNavLink {
|
|||
/**
|
||||
* A url base that legacy apps can set to match deep URLs to an applcation.
|
||||
*
|
||||
* NOTE: this should be removed once legacy apps are gone.
|
||||
* @internalRemarks
|
||||
* This should be removed once legacy apps are gone.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
readonly subUrlBase?: string;
|
||||
|
||||
/**
|
||||
* Whether or not the subUrl feature should be enabled.
|
||||
*
|
||||
* NOTE: only read by legacy platform.
|
||||
* @internalRemarks
|
||||
* Only read by legacy platform.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
readonly linkToLastSubUrl?: boolean;
|
||||
|
||||
/**
|
||||
* A url that legacy apps can set to deep link into their applications.
|
||||
*
|
||||
* NOTE: Currently used by the "lastSubUrl" feature legacy/ui/chrome. This should
|
||||
* @internalRemarks
|
||||
* Currently used by the "lastSubUrl" feature legacy/ui/chrome. This should
|
||||
* be removed once the ApplicationService is implemented and mounting apps. At that
|
||||
* time, each app can handle opening to the previous location when they are mounted.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
readonly url?: string;
|
||||
|
||||
/**
|
||||
* Indicates whether or not this app is currently on the screen.
|
||||
*
|
||||
* @internalRemarks
|
||||
* Remove this when ApplicationService is implemented and managing apps.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
readonly active?: boolean;
|
||||
|
||||
/**
|
||||
* Disables a link from being clickable.
|
||||
*
|
||||
* @internalRemarks
|
||||
* This is only used by the ML and Graph plugins currently. They use this field
|
||||
* to disable the nav link when the license is expired.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
readonly disabled?: boolean;
|
||||
|
||||
/**
|
||||
* Hides a link from the navigation.
|
||||
*
|
||||
* @internalRemarks
|
||||
* Remove this when ApplicationService is implemented. Instead, plugins should only
|
||||
* register an Application if needed.
|
||||
*/
|
||||
readonly hidden?: boolean;
|
||||
|
||||
/**
|
||||
* Used to separate links to legacy applications from NP applications
|
||||
* @internal
|
||||
*/
|
||||
readonly legacy: boolean;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -21,10 +21,17 @@ import { NavLinksService } from './nav_links_service';
|
|||
import { take, map, takeLast } from 'rxjs/operators';
|
||||
|
||||
const mockAppService = {
|
||||
availableApps: [
|
||||
{ id: 'app1', order: 0, title: 'App 1', icon: 'app1', rootRoute: '/app1' },
|
||||
{ id: 'app2', order: -10, title: 'App 2', euiIconType: 'canvasApp', rootRoute: '/app2' },
|
||||
{ id: 'legacyApp', order: 20, title: 'Legacy App', appUrl: '/legacy-app' },
|
||||
availableApps: [],
|
||||
availableLegacyApps: [
|
||||
{ id: 'legacyApp1', order: 0, title: 'Legacy App 1', icon: 'legacyApp1', appUrl: '/app1' },
|
||||
{
|
||||
id: 'legacyApp2',
|
||||
order: -10,
|
||||
title: 'Legacy App 2',
|
||||
euiIconType: 'canvasApp',
|
||||
appUrl: '/app2',
|
||||
},
|
||||
{ id: 'legacyApp3', order: 20, title: 'Legacy App 3', appUrl: '/app3' },
|
||||
],
|
||||
} as any;
|
||||
|
||||
|
@ -53,17 +60,20 @@ describe('NavLinksService', () => {
|
|||
map(links => links.map(l => l.id))
|
||||
)
|
||||
.toPromise()
|
||||
).toEqual(['app2', 'app1', 'legacyApp']);
|
||||
).toEqual(['legacyApp2', 'legacyApp1', 'legacyApp3']);
|
||||
});
|
||||
|
||||
it('emits multiple values', async () => {
|
||||
const navLinkIds$ = start.getNavLinks$().pipe(map(links => links.map(l => l.id)));
|
||||
const emittedLinks: string[][] = [];
|
||||
navLinkIds$.subscribe(r => emittedLinks.push(r));
|
||||
start.update('app1', { active: true });
|
||||
start.update('legacyApp1', { active: true });
|
||||
|
||||
service.stop();
|
||||
expect(emittedLinks).toEqual([['app2', 'app1', 'legacyApp'], ['app2', 'app1', 'legacyApp']]);
|
||||
expect(emittedLinks).toEqual([
|
||||
['legacyApp2', 'legacyApp1', 'legacyApp3'],
|
||||
['legacyApp2', 'legacyApp1', 'legacyApp3'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('completes when service is stopped', async () => {
|
||||
|
@ -78,7 +88,7 @@ describe('NavLinksService', () => {
|
|||
|
||||
describe('#get()', () => {
|
||||
it('returns link if exists', () => {
|
||||
expect(start.get('app1')!.title).toEqual('App 1');
|
||||
expect(start.get('legacyApp1')!.title).toEqual('Legacy App 1');
|
||||
});
|
||||
|
||||
it('returns undefined if it does not exist', () => {
|
||||
|
@ -88,13 +98,13 @@ describe('NavLinksService', () => {
|
|||
|
||||
describe('#getAll()', () => {
|
||||
it('returns a sorted array of navlinks', () => {
|
||||
expect(start.getAll().map(l => l.id)).toEqual(['app2', 'app1', 'legacyApp']);
|
||||
expect(start.getAll().map(l => l.id)).toEqual(['legacyApp2', 'legacyApp1', 'legacyApp3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#has()', () => {
|
||||
it('returns true if exists', () => {
|
||||
expect(start.has('app1')).toBe(true);
|
||||
expect(start.has('legacyApp1')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if it does not exist', () => {
|
||||
|
@ -113,11 +123,11 @@ describe('NavLinksService', () => {
|
|||
map(links => links.map(l => l.id))
|
||||
)
|
||||
.toPromise()
|
||||
).toEqual(['app2', 'app1', 'legacyApp']);
|
||||
).toEqual(['legacyApp2', 'legacyApp1', 'legacyApp3']);
|
||||
});
|
||||
|
||||
it('removes all other links', async () => {
|
||||
start.showOnly('app1');
|
||||
start.showOnly('legacyApp1');
|
||||
expect(
|
||||
await start
|
||||
.getNavLinks$()
|
||||
|
@ -126,23 +136,24 @@ describe('NavLinksService', () => {
|
|||
map(links => links.map(l => l.id))
|
||||
)
|
||||
.toPromise()
|
||||
).toEqual(['app1']);
|
||||
).toEqual(['legacyApp1']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#update()', () => {
|
||||
it('updates the navlinks and returns the updated link', async () => {
|
||||
expect(start.update('app1', { hidden: true })).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"baseUrl": "http://localhost/wow/app1",
|
||||
"hidden": true,
|
||||
"icon": "app1",
|
||||
"id": "app1",
|
||||
"order": 0,
|
||||
"rootRoute": "/app1",
|
||||
"title": "App 1",
|
||||
}
|
||||
`);
|
||||
expect(start.update('legacyApp1', { hidden: true })).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"appUrl": "/app1",
|
||||
"baseUrl": "http://localhost/wow/app1",
|
||||
"hidden": true,
|
||||
"icon": "legacyApp1",
|
||||
"id": "legacyApp1",
|
||||
"legacy": true,
|
||||
"order": 0,
|
||||
"title": "Legacy App 1",
|
||||
}
|
||||
`);
|
||||
const hiddenLinkIds = await start
|
||||
.getNavLinks$()
|
||||
.pipe(
|
||||
|
@ -150,7 +161,7 @@ Object {
|
|||
map(links => links.filter(l => l.hidden).map(l => l.id))
|
||||
)
|
||||
.toPromise();
|
||||
expect(hiddenLinkIds).toEqual(['app1']);
|
||||
expect(hiddenLinkIds).toEqual(['legacyApp1']);
|
||||
});
|
||||
|
||||
it('returns undefined if link does not exist', () => {
|
||||
|
|
|
@ -99,20 +99,20 @@ export class NavLinksService {
|
|||
private readonly stop$ = new ReplaySubject(1);
|
||||
|
||||
public start({ application, http }: StartDeps): ChromeNavLinks {
|
||||
const legacyAppLinks = application.availableLegacyApps.map(
|
||||
app =>
|
||||
[
|
||||
app.id,
|
||||
new NavLinkWrapper({
|
||||
...app,
|
||||
legacy: true,
|
||||
baseUrl: relativeToAbsolute(http.basePath.prepend(app.appUrl)),
|
||||
}),
|
||||
] as [string, NavLinkWrapper]
|
||||
);
|
||||
|
||||
const navLinks$ = new BehaviorSubject<ReadonlyMap<string, NavLinkWrapper>>(
|
||||
new Map(
|
||||
application.availableApps.map(
|
||||
app =>
|
||||
[
|
||||
app.id,
|
||||
new NavLinkWrapper({
|
||||
...app,
|
||||
// Either rootRoute or appUrl must be defined.
|
||||
baseUrl: relativeToAbsolute(http.basePath.prepend((app.rootRoute || app.appUrl)!)),
|
||||
}),
|
||||
] as [string, NavLinkWrapper]
|
||||
)
|
||||
)
|
||||
new Map(legacyAppLinks)
|
||||
);
|
||||
const forceAppSwitcherNavigation$ = new BehaviorSubject(false);
|
||||
|
||||
|
|
|
@ -20,18 +20,12 @@ export interface ApplicationSetup {
|
|||
registerLegacyApp(app: LegacyApp): void;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ApplicationStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export interface ApplicationStart {
|
||||
// Warning: (ae-forgotten-export) The symbol "CapabilitiesStart" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
availableApps: CapabilitiesStart['availableApps'];
|
||||
// (undocumented)
|
||||
capabilities: CapabilitiesStart['capabilities'];
|
||||
// (undocumented)
|
||||
mount: (mountHandler: Function) => void;
|
||||
availableApps: readonly App[];
|
||||
// @internal
|
||||
availableLegacyApps: readonly LegacyApp[];
|
||||
capabilities: RecursiveReadonly<Capabilities>;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
@ -95,18 +89,25 @@ export interface ChromeNavControls {
|
|||
|
||||
// @public (undocumented)
|
||||
export interface ChromeNavLink {
|
||||
// @deprecated
|
||||
readonly active?: boolean;
|
||||
readonly baseUrl: string;
|
||||
// @deprecated
|
||||
readonly disabled?: boolean;
|
||||
readonly euiIconType?: string;
|
||||
readonly hidden?: boolean;
|
||||
readonly icon?: string;
|
||||
readonly id: string;
|
||||
// @internal
|
||||
readonly legacy: boolean;
|
||||
// @deprecated
|
||||
readonly linkToLastSubUrl?: boolean;
|
||||
readonly order: number;
|
||||
// @deprecated
|
||||
readonly subUrlBase?: string;
|
||||
readonly title: string;
|
||||
readonly tooltip?: string;
|
||||
// @deprecated
|
||||
readonly url?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ describe('chrome nav apis', function () {
|
|||
url: `${appUrl}?id=${deletedId}`,
|
||||
baseUrl: appUrl,
|
||||
linkToLastSubUrl: true,
|
||||
legacy: true,
|
||||
}];
|
||||
|
||||
const { chrome } = init({ appUrlStore });
|
||||
|
@ -98,7 +99,8 @@ describe('chrome nav apis', function () {
|
|||
title: 'Discover',
|
||||
url: lastUrl,
|
||||
baseUrl: appUrl,
|
||||
linkToLastSubUrl: true
|
||||
linkToLastSubUrl: true,
|
||||
legacy: true,
|
||||
}];
|
||||
|
||||
const { chrome } = init({ appUrlStore });
|
||||
|
@ -114,17 +116,20 @@ describe('chrome nav apis', function () {
|
|||
{
|
||||
id: 'kibana:discover',
|
||||
baseUrl: `${baseUrl}/app/kibana#discover`,
|
||||
subUrlBase: '/app/kibana#discover'
|
||||
subUrlBase: '/app/kibana#discover',
|
||||
legacy: true,
|
||||
},
|
||||
{
|
||||
id: 'kibana:visualize',
|
||||
baseUrl: `${baseUrl}/app/kibana#visualize`,
|
||||
subUrlBase: '/app/kibana#visualize'
|
||||
subUrlBase: '/app/kibana#visualize',
|
||||
legacy: true,
|
||||
},
|
||||
{
|
||||
id: 'kibana:dashboard',
|
||||
baseUrl: `${baseUrl}/app/kibana#dashboards`,
|
||||
subUrlBase: '/app/kibana#dashboard'
|
||||
subUrlBase: '/app/kibana#dashboard',
|
||||
legacy: true,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -150,6 +155,7 @@ describe('chrome nav apis', function () {
|
|||
baseUrl: `${baseUrl}/app/kibana#visualize`,
|
||||
url: `${baseUrl}/app/kibana#visualize`,
|
||||
subUrlBase: '/app/kibana#visualize',
|
||||
legacy: true,
|
||||
}];
|
||||
|
||||
const { chrome } = init({ appUrlStore });
|
||||
|
|
|
@ -78,7 +78,7 @@ export function initChromeNavApi(chrome: any, internals: NavInternals) {
|
|||
coreNavLinks
|
||||
.getAll()
|
||||
// Filter only legacy links
|
||||
.filter(link => link.subUrlBase)
|
||||
.filter(link => link.legacy)
|
||||
.forEach(link => {
|
||||
const active = url.startsWith(link.subUrlBase!);
|
||||
link = coreNavLinks.update(link.id, { active })!;
|
||||
|
|
7
typings/index.d.ts
vendored
7
typings/index.d.ts
vendored
|
@ -30,3 +30,10 @@ type MethodKeysOf<T> = {
|
|||
type PublicMethodsOf<T> = Pick<T, MethodKeysOf<T>>;
|
||||
|
||||
type MockedKeys<T> = { [P in keyof T]: jest.Mocked<T[P]> };
|
||||
|
||||
type DeeplyMockedKeys<T> = {
|
||||
[P in keyof T]: T[P] extends (...args: any[]) => any
|
||||
? jest.MockInstance<ReturnType<T[P]>, Parameters<T[P]>>
|
||||
: DeeplyMockedKeys<T[P]>;
|
||||
} &
|
||||
T;
|
||||
|
|
7
x-pack/typings/index.d.ts
vendored
7
x-pack/typings/index.d.ts
vendored
|
@ -25,6 +25,13 @@ declare module 'axios/lib/adapters/xhr';
|
|||
|
||||
type MockedKeys<T> = { [P in keyof T]: jest.Mocked<T[P]> };
|
||||
|
||||
type DeeplyMockedKeys<T> = {
|
||||
[P in keyof T]: T[P] extends (...args: any[]) => any
|
||||
? jest.MockInstance<ReturnType<T[P]>, Parameters<T[P]>>
|
||||
: DeeplyMockedKeys<T[P]>;
|
||||
} &
|
||||
T;
|
||||
|
||||
// allow JSON files to be imported directly without lint errors
|
||||
// see: https://github.com/palantir/tslint/issues/1264#issuecomment-228433367
|
||||
// and: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#arbitrary-expressions-are-forbidden-in-export-assignments-in-ambient-contexts
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue