mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[7.x] [new-platform] Introduce ApplicationService scaffolding and capabilities loading (#35545) (#36154)
This commit is contained in:
parent
9e0687a4e0
commit
d6f15af6e5
98 changed files with 1783 additions and 742 deletions
|
@ -0,0 +1,19 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationSetup](./kibana-plugin-public.applicationsetup.md)
|
||||
|
||||
## ApplicationSetup interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface ApplicationSetup
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [registerApp(app)](./kibana-plugin-public.applicationsetup.registerapp.md) | Register an mountable application to the system. Apps will be mounted based on their <code>rootRoute</code>. |
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) > [registerApp](./kibana-plugin-public.applicationsetup.registerapp.md)
|
||||
|
||||
## ApplicationSetup.registerApp() method
|
||||
|
||||
Register an mountable application to the system. Apps will be mounted based on their `rootRoute`<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
registerApp(app: App): void;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| app | <code>App</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`void`
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationStart](./kibana-plugin-public.applicationstart.md) > [availableApps](./kibana-plugin-public.applicationstart.availableapps.md)
|
||||
|
||||
## ApplicationStart.availableApps property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
availableApps: CapabilitiesStart['availableApps'];
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationStart](./kibana-plugin-public.applicationstart.md) > [capabilities](./kibana-plugin-public.applicationstart.capabilities.md)
|
||||
|
||||
## ApplicationStart.capabilities property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
capabilities: CapabilitiesStart['capabilities'];
|
||||
```
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationStart](./kibana-plugin-public.applicationstart.md)
|
||||
|
||||
## ApplicationStart interface
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface ApplicationStart
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| 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> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [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;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [BasePathStart](./kibana-plugin-public.basepathstart.md)
|
||||
|
||||
## BasePathStart type
|
||||
|
||||
Provides access to the 'server.basePath' configuration option in kibana.yml
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type BasePathStart = BasePathSetup;
|
||||
```
|
|
@ -1,13 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [CapabilitiesStart](./kibana-plugin-public.capabilitiesstart.md) > [getCapabilities](./kibana-plugin-public.capabilitiesstart.getcapabilities.md)
|
||||
|
||||
## CapabilitiesStart.getCapabilities property
|
||||
|
||||
Gets the read-only capabilities.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getCapabilities: () => Capabilities;
|
||||
```
|
|
@ -1,20 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [CapabilitiesStart](./kibana-plugin-public.capabilitiesstart.md)
|
||||
|
||||
## CapabilitiesStart interface
|
||||
|
||||
Capabilities Setup.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface CapabilitiesStart
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [getCapabilities](./kibana-plugin-public.capabilitiesstart.getcapabilities.md) | <code>() => Capabilities</code> | Gets the read-only capabilities. |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreSetup](./kibana-plugin-public.coresetup.md) > [application](./kibana-plugin-public.coresetup.application.md)
|
||||
|
||||
## CoreSetup.application property
|
||||
|
||||
[ApplicationSetup](./kibana-plugin-public.applicationsetup.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
application: ApplicationSetup;
|
||||
```
|
|
@ -16,6 +16,7 @@ export interface CoreSetup
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [application](./kibana-plugin-public.coresetup.application.md) | <code>ApplicationSetup</code> | [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) |
|
||||
| [basePath](./kibana-plugin-public.coresetup.basepath.md) | <code>BasePathSetup</code> | [BasePathSetup](./kibana-plugin-public.basepathsetup.md) |
|
||||
| [chrome](./kibana-plugin-public.coresetup.chrome.md) | <code>ChromeSetup</code> | [ChromeSetup](./kibana-plugin-public.chromesetup.md) |
|
||||
| [fatalErrors](./kibana-plugin-public.coresetup.fatalerrors.md) | <code>FatalErrorsSetup</code> | [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) |
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreStart](./kibana-plugin-public.corestart.md) > [capabilities](./kibana-plugin-public.corestart.capabilities.md)
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreStart](./kibana-plugin-public.corestart.md) > [application](./kibana-plugin-public.corestart.application.md)
|
||||
|
||||
## CoreStart.capabilities property
|
||||
## CoreStart.application property
|
||||
|
||||
[CapabilitiesStart](./kibana-plugin-public.capabilitiesstart.md)
|
||||
[ApplicationStart](./kibana-plugin-public.applicationstart.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
capabilities: CapabilitiesStart;
|
||||
application: ApplicationStart;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreStart](./kibana-plugin-public.corestart.md) > [basePath](./kibana-plugin-public.corestart.basepath.md)
|
||||
|
||||
## CoreStart.basePath property
|
||||
|
||||
[BasePathStart](./kibana-plugin-public.basepathstart.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
basePath: BasePathStart;
|
||||
```
|
|
@ -14,7 +14,8 @@ export interface CoreStart
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [capabilities](./kibana-plugin-public.corestart.capabilities.md) | <code>CapabilitiesStart</code> | [CapabilitiesStart](./kibana-plugin-public.capabilitiesstart.md) |
|
||||
| [application](./kibana-plugin-public.corestart.application.md) | <code>ApplicationStart</code> | [ApplicationStart](./kibana-plugin-public.applicationstart.md) |
|
||||
| [basePath](./kibana-plugin-public.corestart.basepath.md) | <code>BasePathStart</code> | [BasePathStart](./kibana-plugin-public.basepathstart.md) |
|
||||
| [i18n](./kibana-plugin-public.corestart.i18n.md) | <code>I18nStart</code> | [I18nStart](./kibana-plugin-public.i18nstart.md) |
|
||||
| [injectedMetadata](./kibana-plugin-public.corestart.injectedmetadata.md) | <code>InjectedMetadataStart</code> | [InjectedMetadataStart](./kibana-plugin-public.injectedmetadatastart.md) |
|
||||
| [notifications](./kibana-plugin-public.corestart.notifications.md) | <code>NotificationsStart</code> | [NotificationsStart](./kibana-plugin-public.notificationsstart.md) |
|
||||
|
|
|
@ -11,7 +11,7 @@ getLegacyMetadata: () => {
|
|||
app: unknown;
|
||||
translations: unknown;
|
||||
bundleId: string;
|
||||
nav: unknown;
|
||||
nav: LegacyNavLink[];
|
||||
version: string;
|
||||
branch: string;
|
||||
buildNum: number;
|
||||
|
|
|
@ -21,6 +21,6 @@ export interface InjectedMetadataSetup
|
|||
| [getInjectedVar](./kibana-plugin-public.injectedmetadatasetup.getinjectedvar.md) | <code>(name: string, defaultValue?: any) => unknown</code> | |
|
||||
| [getInjectedVars](./kibana-plugin-public.injectedmetadatasetup.getinjectedvars.md) | <code>() => {`<p/>` [key: string]: unknown;`<p/>` }</code> | |
|
||||
| [getKibanaVersion](./kibana-plugin-public.injectedmetadatasetup.getkibanaversion.md) | <code>() => string</code> | |
|
||||
| [getLegacyMetadata](./kibana-plugin-public.injectedmetadatasetup.getlegacymetadata.md) | <code>() => {`<p/>` app: unknown;`<p/>` translations: unknown;`<p/>` bundleId: string;`<p/>` nav: unknown;`<p/>` version: string;`<p/>` branch: string;`<p/>` buildNum: number;`<p/>` buildSha: string;`<p/>` basePath: string;`<p/>` serverName: string;`<p/>` devMode: boolean;`<p/>` uiSettings: {`<p/>` defaults: UiSettingsState;`<p/>` user?: UiSettingsState | undefined;`<p/>` };`<p/>` }</code> | |
|
||||
| [getLegacyMetadata](./kibana-plugin-public.injectedmetadatasetup.getlegacymetadata.md) | <code>() => {`<p/>` app: unknown;`<p/>` translations: unknown;`<p/>` bundleId: string;`<p/>` nav: LegacyNavLink[];`<p/>` version: string;`<p/>` branch: string;`<p/>` buildNum: number;`<p/>` buildSha: string;`<p/>` basePath: string;`<p/>` serverName: string;`<p/>` devMode: boolean;`<p/>` uiSettings: {`<p/>` defaults: UiSettingsState;`<p/>` user?: UiSettingsState | undefined;`<p/>` };`<p/>` }</code> | |
|
||||
| [getPlugins](./kibana-plugin-public.injectedmetadatasetup.getplugins.md) | <code>() => Array<{`<p/>` id: string;`<p/>` plugin: DiscoveredPlugin;`<p/>` }></code> | An array of frontend plugins in topological order. |
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) > [euiIconType](./kibana-plugin-public.legacynavlink.euiicontype.md)
|
||||
|
||||
## LegacyNavLink.euiIconType property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
euiIconType?: string;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) > [icon](./kibana-plugin-public.legacynavlink.icon.md)
|
||||
|
||||
## LegacyNavLink.icon property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
icon?: string;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) > [id](./kibana-plugin-public.legacynavlink.id.md)
|
||||
|
||||
## LegacyNavLink.id property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
id: string;
|
||||
```
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyNavLink](./kibana-plugin-public.legacynavlink.md)
|
||||
|
||||
## LegacyNavLink interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface LegacyNavLink
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [euiIconType](./kibana-plugin-public.legacynavlink.euiicontype.md) | <code>string</code> | |
|
||||
| [icon](./kibana-plugin-public.legacynavlink.icon.md) | <code>string</code> | |
|
||||
| [id](./kibana-plugin-public.legacynavlink.id.md) | <code>string</code> | |
|
||||
| [order](./kibana-plugin-public.legacynavlink.order.md) | <code>number</code> | |
|
||||
| [title](./kibana-plugin-public.legacynavlink.title.md) | <code>string</code> | |
|
||||
| [url](./kibana-plugin-public.legacynavlink.url.md) | <code>string</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) > [order](./kibana-plugin-public.legacynavlink.order.md)
|
||||
|
||||
## LegacyNavLink.order property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
order: number;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) > [title](./kibana-plugin-public.legacynavlink.title.md)
|
||||
|
||||
## LegacyNavLink.title property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
title: string;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) > [url](./kibana-plugin-public.legacynavlink.url.md)
|
||||
|
||||
## LegacyNavLink.url property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
url: string;
|
||||
```
|
|
@ -16,9 +16,10 @@
|
|||
|
||||
| Interface | Description |
|
||||
| --- | --- |
|
||||
| [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | |
|
||||
| [ApplicationStart](./kibana-plugin-public.applicationstart.md) | |
|
||||
| [BasePathSetup](./kibana-plugin-public.basepathsetup.md) | Provides access to the 'server.basePath' configuration option in kibana.yml |
|
||||
| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. |
|
||||
| [CapabilitiesStart](./kibana-plugin-public.capabilitiesstart.md) | Capabilities Setup. |
|
||||
| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | |
|
||||
| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | |
|
||||
| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | |
|
||||
|
@ -27,6 +28,7 @@
|
|||
| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
|
||||
| [I18nSetup](./kibana-plugin-public.i18nsetup.md) | I18nSetup.Context is required by any localizable React component from @<!-- -->kbn/i18n and @<!-- -->elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. |
|
||||
| [InjectedMetadataSetup](./kibana-plugin-public.injectedmetadatasetup.md) | Provides access to the metadata injected by the server into the page |
|
||||
| [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | |
|
||||
| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | |
|
||||
| [OverlayStart](./kibana-plugin-public.overlaystart.md) | |
|
||||
| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
|
||||
|
@ -38,6 +40,7 @@
|
|||
|
||||
| Type Alias | Description |
|
||||
| --- | --- |
|
||||
| [BasePathStart](./kibana-plugin-public.basepathstart.md) | Provides access to the 'server.basePath' configuration option in kibana.yml |
|
||||
| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | |
|
||||
| [ChromeSetup](./kibana-plugin-public.chromesetup.md) | |
|
||||
| [HttpSetup](./kibana-plugin-public.httpsetup.md) | |
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [PluginSetupContext](./kibana-plugin-public.pluginsetupcontext.md) > [http](./kibana-plugin-public.pluginsetupcontext.http.md)
|
||||
|
||||
## PluginSetupContext.http property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
http: HttpSetup;
|
||||
```
|
|
@ -19,6 +19,7 @@ export interface PluginSetupContext
|
|||
| [basePath](./kibana-plugin-public.pluginsetupcontext.basepath.md) | <code>BasePathSetup</code> | |
|
||||
| [chrome](./kibana-plugin-public.pluginsetupcontext.chrome.md) | <code>ChromeSetup</code> | |
|
||||
| [fatalErrors](./kibana-plugin-public.pluginsetupcontext.fatalerrors.md) | <code>FatalErrorsSetup</code> | |
|
||||
| [http](./kibana-plugin-public.pluginsetupcontext.http.md) | <code>HttpSetup</code> | |
|
||||
| [i18n](./kibana-plugin-public.pluginsetupcontext.i18n.md) | <code>I18nSetup</code> | |
|
||||
| [notifications](./kibana-plugin-public.pluginsetupcontext.notifications.md) | <code>NotificationsSetup</code> | |
|
||||
| [uiSettings](./kibana-plugin-public.pluginsetupcontext.uisettings.md) | <code>UiSettingsSetup</code> | |
|
||||
|
|
45
src/core/public/application/application_service.mock.ts
Normal file
45
src/core/public/application/application_service.mock.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { capabilitiesServiceMock } from './capabilities/capabilities_service.mock';
|
||||
import { ApplicationService, ApplicationSetup, ApplicationStart } from './application_service';
|
||||
|
||||
type ApplicationServiceContract = PublicMethodsOf<ApplicationService>;
|
||||
|
||||
const createSetupContractMock = (): jest.Mocked<ApplicationSetup> => ({
|
||||
registerApp: jest.fn(),
|
||||
registerLegacyApp: jest.fn(),
|
||||
});
|
||||
|
||||
const createStartContractMock = (): jest.Mocked<ApplicationStart> => ({
|
||||
mount: jest.fn(),
|
||||
...capabilitiesServiceMock.createStartContract(),
|
||||
});
|
||||
|
||||
const createMock = (): jest.Mocked<ApplicationServiceContract> => ({
|
||||
setup: jest.fn().mockReturnValue(createSetupContractMock()),
|
||||
start: jest.fn().mockReturnValue(createStartContractMock()),
|
||||
stop: jest.fn(),
|
||||
});
|
||||
|
||||
export const applicationServiceMock = {
|
||||
create: createMock,
|
||||
createSetupContract: createSetupContractMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { capabilitiesServiceMock } from './capabilities/capabilities_service.mock';
|
||||
|
||||
export const MockCapabilitiesService = capabilitiesServiceMock.create();
|
||||
export const CapabilitiesServiceConstructor = jest
|
||||
.fn()
|
||||
.mockImplementation(() => MockCapabilitiesService);
|
||||
jest.doMock('./capabilities', () => ({
|
||||
CapabilitiesService: CapabilitiesServiceConstructor,
|
||||
}));
|
73
src/core/public/application/application_service.test.tsx
Normal file
73
src/core/public/application/application_service.test.tsx
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 { basePathServiceMock } from '../base_path/base_path_service.mock';
|
||||
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
|
||||
import { MockCapabilitiesService } from './application_service.test.mocks';
|
||||
import { ApplicationService } from './application_service';
|
||||
|
||||
describe('#start()', () => {
|
||||
it('exposes available apps from capabilities', async () => {
|
||||
const service = new ApplicationService();
|
||||
const setup = service.setup();
|
||||
setup.registerApp({ id: 'app1' } as any);
|
||||
setup.registerLegacyApp({ id: 'app2' } as any);
|
||||
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
|
||||
const basePath = basePathServiceMock.createStartContract();
|
||||
expect((await service.start({ basePath, injectedMetadata })).availableApps)
|
||||
.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "app1",
|
||||
},
|
||||
Object {
|
||||
"id": "app2",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('passes registered applications to capabilities', async () => {
|
||||
const service = new ApplicationService();
|
||||
const setup = service.setup();
|
||||
setup.registerApp({ id: 'app1' } as any);
|
||||
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
|
||||
const basePath = basePathServiceMock.createStartContract();
|
||||
await service.start({ basePath, injectedMetadata });
|
||||
expect(MockCapabilitiesService.start).toHaveBeenCalledWith({
|
||||
apps: [{ id: 'app1' }],
|
||||
basePath,
|
||||
injectedMetadata,
|
||||
});
|
||||
});
|
||||
|
||||
it('passes registered legacy applications to capabilities', async () => {
|
||||
const service = new ApplicationService();
|
||||
const setup = service.setup();
|
||||
setup.registerLegacyApp({ id: 'legacyApp1' } as any);
|
||||
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
|
||||
const basePath = basePathServiceMock.createStartContract();
|
||||
await service.start({ basePath, injectedMetadata });
|
||||
expect(MockCapabilitiesService.start).toHaveBeenCalledWith({
|
||||
apps: [{ id: 'legacyApp1' }],
|
||||
basePath,
|
||||
injectedMetadata,
|
||||
});
|
||||
});
|
||||
});
|
152
src/core/public/application/application_service.tsx
Normal file
152
src/core/public/application/application_service.tsx
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* 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 { Observable, BehaviorSubject } from 'rxjs';
|
||||
import { CapabilitiesStart, CapabilitiesService, Capabilities } from './capabilities';
|
||||
import { InjectedMetadataStart } from '../injected_metadata';
|
||||
import { BasePathStart } from '../base_path';
|
||||
|
||||
interface BaseApp {
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* An ordinal used to sort nav links relative to one another for display.
|
||||
*/
|
||||
order: number;
|
||||
|
||||
/**
|
||||
* The title of the application.
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* An observable for a tooltip shown when hovering over app link.
|
||||
*/
|
||||
tooltip$?: Observable<string>;
|
||||
|
||||
/**
|
||||
* A EUI iconType that will be used for the app's icon. This icon
|
||||
* takes precendence over the `icon` property.
|
||||
*/
|
||||
euiIconType?: string;
|
||||
|
||||
/**
|
||||
* A URL to an image file used as an icon. Used as a fallback
|
||||
* if `euiIconType` is not provided.
|
||||
*/
|
||||
icon?: string;
|
||||
|
||||
/**
|
||||
* Custom capabilities defined by the app.
|
||||
*/
|
||||
capabilities?: Partial<Capabilities>;
|
||||
}
|
||||
|
||||
/** @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.
|
||||
* @returns An unmounting function that will be called to unmount the application.
|
||||
*/
|
||||
mount(targetDomElement: HTMLElement): () => void;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface LegacyApp extends BaseApp {
|
||||
appUrl: string;
|
||||
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type MixedApp = Partial<App> & Partial<LegacyApp> & BaseApp;
|
||||
|
||||
/** @public */
|
||||
export interface ApplicationSetup {
|
||||
/**
|
||||
* Register an mountable application to the system. Apps will be mounted based on their `rootRoute`.
|
||||
* @param app
|
||||
*/
|
||||
registerApp(app: App): void;
|
||||
|
||||
/**
|
||||
* Register metadata about legacy applications. Legacy apps will not be mounted when navigated to.
|
||||
* @param app
|
||||
* @internal
|
||||
*/
|
||||
registerLegacyApp(app: LegacyApp): void;
|
||||
}
|
||||
|
||||
export interface ApplicationStart {
|
||||
mount: (mountHandler: Function) => void;
|
||||
availableApps: CapabilitiesStart['availableApps'];
|
||||
capabilities: CapabilitiesStart['capabilities'];
|
||||
}
|
||||
|
||||
interface StartDeps {
|
||||
basePath: BasePathStart;
|
||||
injectedMetadata: InjectedMetadataStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service that is responsible for registering new applications.
|
||||
* @internal
|
||||
*/
|
||||
export class ApplicationService {
|
||||
private readonly apps$ = new BehaviorSubject<App[]>([]);
|
||||
private readonly legacyApps$ = new BehaviorSubject<LegacyApp[]>([]);
|
||||
private readonly capabilities = new CapabilitiesService();
|
||||
|
||||
public setup(): ApplicationSetup {
|
||||
return {
|
||||
registerApp: (app: App) => {
|
||||
this.apps$.next([...this.apps$.value, app]);
|
||||
},
|
||||
registerLegacyApp: (app: LegacyApp) => {
|
||||
this.legacyApps$.next([...this.legacyApps$.value, app]);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async start({ basePath, injectedMetadata }: StartDeps): Promise<ApplicationStart> {
|
||||
this.apps$.complete();
|
||||
this.legacyApps$.complete();
|
||||
|
||||
const apps = [...this.apps$.value, ...this.legacyApps$.value];
|
||||
const { capabilities, availableApps } = await this.capabilities.start({
|
||||
apps,
|
||||
basePath,
|
||||
injectedMetadata,
|
||||
});
|
||||
|
||||
return {
|
||||
mount() {},
|
||||
capabilities,
|
||||
availableApps,
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
|
@ -16,28 +16,25 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Capabilities, CapabilitiesService, CapabilitiesStart } from './capabilities_service';
|
||||
import { CapabilitiesService, CapabilitiesStart } from './capabilities_service';
|
||||
import { deepFreeze } from '../../utils/deep_freeze';
|
||||
import { MixedApp } from '../application_service';
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract: jest.Mocked<CapabilitiesStart> = {
|
||||
getCapabilities: jest.fn(),
|
||||
};
|
||||
startContract.getCapabilities.mockReturnValue({
|
||||
const createStartContractMock = (
|
||||
apps: ReadonlyArray<MixedApp> = []
|
||||
): jest.Mocked<CapabilitiesStart> => ({
|
||||
availableApps: apps,
|
||||
capabilities: deepFreeze({
|
||||
catalogue: {},
|
||||
management: {},
|
||||
navLinks: {},
|
||||
} as Capabilities);
|
||||
return startContract;
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
type CapabilitiesServiceContract = PublicMethodsOf<CapabilitiesService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<CapabilitiesServiceContract> = {
|
||||
start: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
const createMock = (): jest.Mocked<CapabilitiesServiceContract> => ({
|
||||
start: jest.fn().mockImplementation(({ apps }) => createStartContractMock(apps)),
|
||||
});
|
||||
|
||||
export const capabilitiesServiceMock = {
|
||||
create: createMock,
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import fetchMock from 'fetch-mock/es5/client';
|
||||
|
||||
import { InjectedMetadataService } from '../../injected_metadata';
|
||||
import { CapabilitiesService } from './capabilities_service';
|
||||
import { basePathServiceMock } from '../../base_path/base_path_service.mock';
|
||||
|
||||
describe('#start', () => {
|
||||
const basePath = basePathServiceMock.createStartContract();
|
||||
basePath.addToPath.mockImplementation(str => str);
|
||||
const injectedMetadata = new InjectedMetadataService({
|
||||
injectedMetadata: {
|
||||
vars: {
|
||||
uiCapabilities: {
|
||||
foo: { feature: true },
|
||||
bar: { feature: true },
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
}).start();
|
||||
const apps = [{ id: 'app1' }, { id: 'app2', capabilities: { app2: { feature: true } } }] as any;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.post('/api/capabilities', (url: string, options: any) => ({
|
||||
body: options.body,
|
||||
status: 200,
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('calls backend API with merged capabilities', async () => {
|
||||
const service = new CapabilitiesService();
|
||||
await service.start({ apps, basePath, injectedMetadata });
|
||||
expect(fetchMock.calls()).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"/api/capabilities",
|
||||
Object {
|
||||
"body": "{\\"capabilities\\":{\\"navLinks\\":{\\"app2\\":true,\\"app1\\":true},\\"management\\":{},\\"catalogue\\":{},\\"app2\\":{\\"feature\\":true}}}",
|
||||
"credentials": "same-origin",
|
||||
"headers": Object {
|
||||
"kbn-xsrf": "xxx",
|
||||
},
|
||||
"method": "POST",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns capabilities from backend', async () => {
|
||||
const service = new CapabilitiesService();
|
||||
expect((await service.start({ apps, basePath, injectedMetadata })).capabilities)
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"app2": Object {
|
||||
"feature": true,
|
||||
},
|
||||
"catalogue": Object {},
|
||||
"management": Object {},
|
||||
"navLinks": Object {
|
||||
"app1": true,
|
||||
"app2": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('filters available apps based on returned navLinks', async () => {
|
||||
fetchMock.post(
|
||||
'/api/capabilities',
|
||||
(url: string, options: any) => ({
|
||||
body: JSON.stringify({ capabilities: { navLinks: { app1: true, app2: false } } }),
|
||||
status: 200,
|
||||
}),
|
||||
{ overwriteRoutes: true }
|
||||
);
|
||||
const service = new CapabilitiesService();
|
||||
expect((await service.start({ apps, basePath, injectedMetadata })).availableApps).toEqual([
|
||||
{ id: 'app1' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not allow Capabilities to be modified', async () => {
|
||||
const service = new CapabilitiesService();
|
||||
const { capabilities } = await service.start({
|
||||
apps,
|
||||
basePath,
|
||||
injectedMetadata,
|
||||
});
|
||||
|
||||
// @ts-ignore TypeScript knows this shouldn't be possible
|
||||
expect(() => (capabilities.foo = 'foo')).toThrowError();
|
||||
});
|
||||
});
|
|
@ -16,10 +16,16 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { InjectedMetadataStart } from '../injected_metadata';
|
||||
import { deepFreeze } from '../utils/deep_freeze';
|
||||
|
||||
import { deepFreeze, RecursiveReadonly } from '../../utils/deep_freeze';
|
||||
import { MixedApp } from '../application_service';
|
||||
import { mergeCapabilities } from './merge_capabilities';
|
||||
import { InjectedMetadataStart } from '../../injected_metadata';
|
||||
import { BasePathStart } from '../../base_path';
|
||||
|
||||
interface StartDeps {
|
||||
apps: ReadonlyArray<MixedApp>;
|
||||
basePath: BasePathStart;
|
||||
injectedMetadata: InjectedMetadataStart;
|
||||
}
|
||||
|
||||
|
@ -54,7 +60,13 @@ export interface CapabilitiesStart {
|
|||
/**
|
||||
* Gets the read-only capabilities.
|
||||
*/
|
||||
getCapabilities: () => Capabilities;
|
||||
capabilities: RecursiveReadonly<Capabilities>;
|
||||
|
||||
/**
|
||||
* Apps available based on the current capabilities. Should be used
|
||||
* to show navigation links and make routing decisions.
|
||||
*/
|
||||
availableApps: ReadonlyArray<MixedApp>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -63,10 +75,44 @@ export interface CapabilitiesStart {
|
|||
* Service that is responsible for UI Capabilities.
|
||||
*/
|
||||
export class CapabilitiesService {
|
||||
public start({ injectedMetadata }: StartDeps): CapabilitiesStart {
|
||||
public async start({ apps, basePath, injectedMetadata }: StartDeps): Promise<CapabilitiesStart> {
|
||||
const mergedCapabilities = mergeCapabilities(
|
||||
// Custom capabilites for new platform apps
|
||||
...apps.filter(app => app.capabilities).map(app => app.capabilities!),
|
||||
// Generate navLink capabilities for all apps
|
||||
...apps.map(app => ({ navLinks: { [app.id]: true } }))
|
||||
);
|
||||
|
||||
// NOTE: should replace `fetch` with browser HTTP service once it exists
|
||||
const res = await fetch(basePath.addToPath('/api/capabilities'), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ capabilities: mergedCapabilities }),
|
||||
headers: {
|
||||
'kbn-xsrf': 'xxx',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
if (res.status === 401) {
|
||||
return {
|
||||
availableApps: [],
|
||||
capabilities: deepFreeze({
|
||||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
}),
|
||||
};
|
||||
} else if (res.status !== 200) {
|
||||
throw new Error(`Capabilities check failed.`);
|
||||
}
|
||||
|
||||
const body = await res.json();
|
||||
const capabilities = deepFreeze(body.capabilities as Capabilities);
|
||||
const availableApps = apps.filter(app => capabilities.navLinks[app.id]);
|
||||
|
||||
return {
|
||||
getCapabilities: () =>
|
||||
deepFreeze(injectedMetadata.getInjectedVar('uiCapabilities') as Capabilities),
|
||||
availableApps,
|
||||
capabilities,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { Capabilities } from './capabilities_service';
|
||||
|
||||
export const mergeCapabilities = (...sources: Array<Partial<Capabilities>>) =>
|
||||
sources.reduce(
|
||||
(capabilities, source) => {
|
||||
Object.entries(source).forEach(([key, value]) => {
|
||||
capabilities[key] = {
|
||||
...value,
|
||||
...capabilities[key],
|
||||
};
|
||||
});
|
||||
|
||||
return capabilities;
|
||||
},
|
||||
{
|
||||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
}
|
||||
);
|
21
src/core/public/application/index.ts
Normal file
21
src/core/public/application/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { ApplicationService, ApplicationSetup, ApplicationStart } from './application_service';
|
||||
export { Capabilities } from './capabilities';
|
|
@ -30,16 +30,21 @@ const createSetupContractMock = () => {
|
|||
return setupContract;
|
||||
};
|
||||
|
||||
const createStartContractMock = createSetupContractMock;
|
||||
|
||||
type BasePathServiceContract = PublicMethodsOf<BasePathService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<BasePathServiceContract> = {
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
};
|
||||
mocked.setup.mockReturnValue(createSetupContractMock());
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const basePathServiceMock = {
|
||||
create: createMock,
|
||||
createSetupContract: createSetupContractMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { InjectedMetadataSetup } from '../injected_metadata';
|
||||
import { InjectedMetadataSetup, InjectedMetadataStart } from '../injected_metadata';
|
||||
import { modifyUrl } from '../utils';
|
||||
|
||||
/**
|
||||
|
@ -49,13 +49,24 @@ export interface BasePathSetup {
|
|||
removeFromPath(path: string): string;
|
||||
}
|
||||
|
||||
interface BasePathDeps {
|
||||
/**
|
||||
* Provides access to the 'server.basePath' configuration option in kibana.yml
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type BasePathStart = BasePathSetup;
|
||||
|
||||
interface SetupDeps {
|
||||
injectedMetadata: InjectedMetadataSetup;
|
||||
}
|
||||
|
||||
interface StartDeps {
|
||||
injectedMetadata: InjectedMetadataStart;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class BasePathService {
|
||||
public setup({ injectedMetadata }: BasePathDeps) {
|
||||
public setup({ injectedMetadata }: SetupDeps) {
|
||||
const basePath = injectedMetadata.getBasePath() || '';
|
||||
|
||||
const basePathSetup: BasePathSetup = {
|
||||
|
@ -86,4 +97,8 @@ export class BasePathService {
|
|||
|
||||
return basePathSetup;
|
||||
}
|
||||
|
||||
public start({ injectedMetadata }: StartDeps) {
|
||||
return this.setup({ injectedMetadata });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { BasePathService, BasePathSetup } from './base_path_service';
|
||||
export { BasePathService, BasePathSetup, BasePathStart } from './base_path_service';
|
||||
|
|
|
@ -1,60 +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 { InjectedMetadataService } from '../injected_metadata';
|
||||
import { CapabilitiesService } from './capabilities_service';
|
||||
|
||||
describe('#start', () => {
|
||||
it('returns a service with getCapabilities', () => {
|
||||
const injectedMetadata = new InjectedMetadataService({
|
||||
injectedMetadata: {
|
||||
vars: {
|
||||
uiCapabilities: {
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
});
|
||||
const service = new CapabilitiesService();
|
||||
const startContract = service.start({ injectedMetadata: injectedMetadata.start() });
|
||||
expect(startContract.getCapabilities()).toEqual({
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
});
|
||||
});
|
||||
|
||||
it(`does not allow Capabilities to be modified`, () => {
|
||||
const injectedMetadata = new InjectedMetadataService({
|
||||
injectedMetadata: {
|
||||
vars: {
|
||||
uiCapabilities: {
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
});
|
||||
const service = new CapabilitiesService();
|
||||
const startContract = service.start({ injectedMetadata: injectedMetadata.start() });
|
||||
const capabilities = startContract.getCapabilities();
|
||||
|
||||
// @ts-ignore TypeScript knows this shouldn't be possible
|
||||
expect(() => (capabilities.foo = 'foo')).toThrowError();
|
||||
});
|
||||
});
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { basePathServiceMock } from './base_path/base_path_service.mock';
|
||||
import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock';
|
||||
import { applicationServiceMock } from './application/application_service.mock';
|
||||
import { chromeServiceMock } from './chrome/chrome_service.mock';
|
||||
import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock';
|
||||
import { httpServiceMock } from './http/http_service.mock';
|
||||
|
@ -106,10 +106,10 @@ jest.doMock('./plugins', () => ({
|
|||
PluginsService: PluginsServiceConstructor,
|
||||
}));
|
||||
|
||||
export const MockCapabilitiesService = capabilitiesServiceMock.create();
|
||||
export const CapabilitiesServiceConstructor = jest
|
||||
export const MockApplicationService = applicationServiceMock.create();
|
||||
export const ApplicationServiceConstructor = jest
|
||||
.fn()
|
||||
.mockImplementation(() => MockCapabilitiesService);
|
||||
jest.doMock('./capabilities', () => ({
|
||||
CapabilitiesService: CapabilitiesServiceConstructor,
|
||||
.mockImplementation(() => MockApplicationService);
|
||||
jest.doMock('./application', () => ({
|
||||
ApplicationService: ApplicationServiceConstructor,
|
||||
}));
|
||||
|
|
|
@ -39,7 +39,7 @@ import {
|
|||
NotificationServiceConstructor,
|
||||
OverlayServiceConstructor,
|
||||
UiSettingsServiceConstructor,
|
||||
MockCapabilitiesService,
|
||||
MockApplicationService,
|
||||
} from './core_system.test.mocks';
|
||||
|
||||
import { CoreSystem } from './core_system';
|
||||
|
@ -155,6 +155,11 @@ describe('#setup()', () => {
|
|||
return core.setup();
|
||||
}
|
||||
|
||||
it('calls application#setup()', async () => {
|
||||
await setupCore();
|
||||
expect(MockApplicationService.setup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls injectedMetadata#setup()', async () => {
|
||||
await setupCore();
|
||||
expect(MockInjectedMetadataService.setup).toHaveBeenCalledTimes(1);
|
||||
|
@ -219,9 +224,9 @@ describe('#start()', () => {
|
|||
expect(root.innerHTML).toBe('<div></div><div></div><div></div>');
|
||||
});
|
||||
|
||||
it('calls capabilities#start()', async () => {
|
||||
it('calls application#start()', async () => {
|
||||
await startCore();
|
||||
expect(MockCapabilitiesService.start).toHaveBeenCalledTimes(1);
|
||||
expect(MockApplicationService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls i18n#start()', async () => {
|
||||
|
|
|
@ -21,7 +21,6 @@ import './core.css';
|
|||
|
||||
import { CoreSetup, CoreStart } from '.';
|
||||
import { BasePathService } from './base_path';
|
||||
import { CapabilitiesService } from './capabilities';
|
||||
import { ChromeService } from './chrome';
|
||||
import { FatalErrorsService } from './fatal_errors';
|
||||
import { HttpService } from './http';
|
||||
|
@ -32,6 +31,7 @@ import { NotificationsService } from './notifications';
|
|||
import { OverlayService } from './overlays';
|
||||
import { PluginsService } from './plugins';
|
||||
import { UiSettingsService } from './ui_settings';
|
||||
import { ApplicationService } from './application';
|
||||
|
||||
interface Params {
|
||||
rootDomElement: HTMLElement;
|
||||
|
@ -63,9 +63,9 @@ export class CoreSystem {
|
|||
private readonly basePath: BasePathService;
|
||||
private readonly chrome: ChromeService;
|
||||
private readonly i18n: I18nService;
|
||||
private readonly capabilities: CapabilitiesService;
|
||||
private readonly overlay: OverlayService;
|
||||
private readonly plugins: PluginsService;
|
||||
private readonly application: ApplicationService;
|
||||
|
||||
private readonly rootDomElement: HTMLElement;
|
||||
private readonly overlayTargetDomElement: HTMLDivElement;
|
||||
|
@ -83,8 +83,6 @@ export class CoreSystem {
|
|||
|
||||
this.i18n = new I18nService();
|
||||
|
||||
this.capabilities = new CapabilitiesService();
|
||||
|
||||
this.injectedMetadata = new InjectedMetadataService({
|
||||
injectedMetadata,
|
||||
});
|
||||
|
@ -103,6 +101,7 @@ export class CoreSystem {
|
|||
this.uiSettings = new UiSettingsService();
|
||||
this.overlayTargetDomElement = document.createElement('div');
|
||||
this.overlay = new OverlayService(this.overlayTargetDomElement);
|
||||
this.application = new ApplicationService();
|
||||
this.chrome = new ChromeService({ browserSupportsCsp });
|
||||
|
||||
const core: CoreContext = {};
|
||||
|
@ -127,12 +126,14 @@ export class CoreSystem {
|
|||
basePath,
|
||||
});
|
||||
const notifications = this.notifications.setup({ uiSettings });
|
||||
const application = this.application.setup();
|
||||
const chrome = this.chrome.setup({
|
||||
injectedMetadata,
|
||||
notifications,
|
||||
});
|
||||
|
||||
const core: CoreSetup = {
|
||||
application,
|
||||
basePath,
|
||||
chrome,
|
||||
fatalErrors,
|
||||
|
@ -155,27 +156,30 @@ export class CoreSystem {
|
|||
|
||||
public async start() {
|
||||
try {
|
||||
// ensure the rootDomElement is empty
|
||||
this.rootDomElement.textContent = '';
|
||||
this.rootDomElement.classList.add('coreSystemRootDomElement');
|
||||
const injectedMetadata = await this.injectedMetadata.start();
|
||||
const basePath = await this.basePath.start({ injectedMetadata });
|
||||
const i18n = await this.i18n.start();
|
||||
const application = await this.application.start({ basePath, injectedMetadata });
|
||||
|
||||
const notificationsTargetDomElement = document.createElement('div');
|
||||
const legacyPlatformTargetDomElement = document.createElement('div');
|
||||
|
||||
// ensure the rootDomElement is empty
|
||||
this.rootDomElement.textContent = '';
|
||||
this.rootDomElement.classList.add('coreSystemRootDomElement');
|
||||
this.rootDomElement.appendChild(notificationsTargetDomElement);
|
||||
this.rootDomElement.appendChild(legacyPlatformTargetDomElement);
|
||||
this.rootDomElement.appendChild(this.overlayTargetDomElement);
|
||||
|
||||
const injectedMetadata = this.injectedMetadata.start();
|
||||
const i18n = this.i18n.start();
|
||||
const capabilities = this.capabilities.start({ injectedMetadata });
|
||||
const notifications = this.notifications.start({
|
||||
const notifications = await this.notifications.start({
|
||||
i18n,
|
||||
targetDomElement: notificationsTargetDomElement,
|
||||
});
|
||||
const overlays = this.overlay.start({ i18n });
|
||||
const overlays = await this.overlay.start({ i18n });
|
||||
|
||||
const core: CoreStart = {
|
||||
capabilities,
|
||||
application,
|
||||
basePath,
|
||||
i18n,
|
||||
injectedMetadata,
|
||||
notifications,
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { BasePathSetup } from './base_path';
|
||||
import { Capabilities, CapabilitiesStart } from './capabilities';
|
||||
import { BasePathSetup, BasePathStart } from './base_path';
|
||||
import {
|
||||
ChromeBadge,
|
||||
ChromeBrand,
|
||||
|
@ -33,6 +32,7 @@ import {
|
|||
InjectedMetadataParams,
|
||||
InjectedMetadataSetup,
|
||||
InjectedMetadataStart,
|
||||
LegacyNavLink,
|
||||
} from './injected_metadata';
|
||||
import {
|
||||
NotificationsSetup,
|
||||
|
@ -44,6 +44,7 @@ import {
|
|||
import { FlyoutRef, OverlayStart } from './overlays';
|
||||
import { Plugin, PluginInitializer, PluginInitializerContext, PluginSetupContext } from './plugins';
|
||||
import { UiSettingsClient, UiSettingsSetup, UiSettingsState } from './ui_settings';
|
||||
import { ApplicationSetup, Capabilities, ApplicationStart } from './application';
|
||||
|
||||
/** @interal */
|
||||
export { CoreContext, CoreSystem } from './core_system';
|
||||
|
@ -58,6 +59,8 @@ export { CoreContext, CoreSystem } from './core_system';
|
|||
* https://github.com/Microsoft/web-build-tools/issues/1237
|
||||
*/
|
||||
export interface CoreSetup {
|
||||
/** {@link ApplicationSetup} */
|
||||
application: ApplicationSetup;
|
||||
/** {@link I18nSetup} */
|
||||
i18n: I18nSetup;
|
||||
/** {@link InjectedMetadataSetup} */
|
||||
|
@ -77,8 +80,10 @@ export interface CoreSetup {
|
|||
}
|
||||
|
||||
export interface CoreStart {
|
||||
/** {@link CapabilitiesStart} */
|
||||
capabilities: CapabilitiesStart;
|
||||
/** {@link ApplicationStart} */
|
||||
application: ApplicationStart;
|
||||
/** {@link BasePathStart} */
|
||||
basePath: BasePathStart;
|
||||
/** {@link I18nStart} */
|
||||
i18n: I18nStart;
|
||||
/** {@link InjectedMetadataStart} */
|
||||
|
@ -90,11 +95,13 @@ export interface CoreStart {
|
|||
}
|
||||
|
||||
export {
|
||||
ApplicationSetup,
|
||||
ApplicationStart,
|
||||
BasePathSetup,
|
||||
BasePathStart,
|
||||
HttpSetup,
|
||||
FatalErrorsSetup,
|
||||
Capabilities,
|
||||
CapabilitiesStart,
|
||||
ChromeSetup,
|
||||
ChromeBadge,
|
||||
ChromeBreadcrumb,
|
||||
|
@ -105,6 +112,7 @@ export {
|
|||
InjectedMetadataSetup,
|
||||
InjectedMetadataStart,
|
||||
InjectedMetadataParams,
|
||||
LegacyNavLink,
|
||||
Plugin,
|
||||
PluginInitializer,
|
||||
PluginInitializerContext,
|
||||
|
|
|
@ -22,4 +22,5 @@ export {
|
|||
InjectedMetadataParams,
|
||||
InjectedMetadataSetup,
|
||||
InjectedMetadataStart,
|
||||
LegacyNavLink,
|
||||
} from './injected_metadata_service';
|
||||
|
|
|
@ -31,6 +31,7 @@ const createSetupContractMock = () => {
|
|||
setupContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true });
|
||||
setupContract.getKibanaVersion.mockReturnValue('kibanaVersion');
|
||||
setupContract.getLegacyMetadata.mockReturnValue({
|
||||
nav: [],
|
||||
uiSettings: {
|
||||
defaults: { legacyInjectedUiSettingDefaults: true },
|
||||
user: { legacyInjectedUiSettingUserValues: true },
|
||||
|
|
|
@ -22,6 +22,16 @@ import { DiscoveredPlugin, PluginName } from '../../server';
|
|||
import { UiSettingsState } from '../ui_settings';
|
||||
import { deepFreeze } from '../utils/deep_freeze';
|
||||
|
||||
/** @public */
|
||||
export interface LegacyNavLink {
|
||||
id: string;
|
||||
title: string;
|
||||
order: number;
|
||||
url: string;
|
||||
icon?: string;
|
||||
euiIconType?: string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface InjectedMetadataParams {
|
||||
injectedMetadata: {
|
||||
|
@ -42,7 +52,7 @@ export interface InjectedMetadataParams {
|
|||
app: unknown;
|
||||
translations: unknown;
|
||||
bundleId: string;
|
||||
nav: unknown;
|
||||
nav: LegacyNavLink[];
|
||||
version: string;
|
||||
branch: string;
|
||||
buildNum: number;
|
||||
|
@ -140,7 +150,7 @@ export interface InjectedMetadataSetup {
|
|||
app: unknown;
|
||||
translations: unknown;
|
||||
bundleId: string;
|
||||
nav: unknown;
|
||||
nav: LegacyNavLink[];
|
||||
version: string;
|
||||
branch: string;
|
||||
buildNum: number;
|
||||
|
|
|
@ -150,7 +150,6 @@ jest.mock('ui/chrome/services/global_nav_state', () => {
|
|||
});
|
||||
|
||||
import { basePathServiceMock } from '../base_path/base_path_service.mock';
|
||||
import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock';
|
||||
import { chromeServiceMock } from '../chrome/chrome_service.mock';
|
||||
import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
|
@ -160,7 +159,9 @@ import { notificationServiceMock } from '../notifications/notifications_service.
|
|||
import { overlayServiceMock } from '../overlays/overlay_service.mock';
|
||||
import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
|
||||
import { LegacyPlatformService } from './legacy_service';
|
||||
import { applicationServiceMock } from '../application/application_service.mock';
|
||||
|
||||
const applicationSetup = applicationServiceMock.createSetupContract();
|
||||
const basePathSetup = basePathServiceMock.createSetupContract();
|
||||
const chromeSetup = chromeServiceMock.createSetupContract();
|
||||
const fatalErrorsSetup = fatalErrorsServiceMock.createSetupContract();
|
||||
|
@ -178,6 +179,7 @@ const defaultParams = {
|
|||
|
||||
const defaultSetupDeps = {
|
||||
core: {
|
||||
application: applicationSetup,
|
||||
i18n: i18nSetup,
|
||||
fatalErrors: fatalErrorsSetup,
|
||||
injectedMetadata: injectedMetadataSetup,
|
||||
|
@ -189,7 +191,8 @@ const defaultSetupDeps = {
|
|||
},
|
||||
};
|
||||
|
||||
const capabilitiesStart = capabilitiesServiceMock.createStartContract();
|
||||
const applicationStart = applicationServiceMock.createStartContract();
|
||||
const basePathStart = basePathServiceMock.createStartContract();
|
||||
const i18nStart = i18nServiceMock.createStartContract();
|
||||
const injectedMetadataStart = injectedMetadataServiceMock.createStartContract();
|
||||
const notificationsStart = notificationServiceMock.createStartContract();
|
||||
|
@ -197,7 +200,8 @@ const overlayStart = overlayServiceMock.createStartContract();
|
|||
|
||||
const defaultStartDeps = {
|
||||
core: {
|
||||
capabilities: capabilitiesStart,
|
||||
application: applicationStart,
|
||||
basePath: basePathStart,
|
||||
i18n: i18nStart,
|
||||
injectedMetadata: injectedMetadataStart,
|
||||
notifications: notificationsStart,
|
||||
|
@ -208,7 +212,6 @@ const defaultStartDeps = {
|
|||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
injectedMetadataSetup.getLegacyMetadata.mockReset();
|
||||
jest.resetModules();
|
||||
mockLoadOrder.length = 0;
|
||||
});
|
||||
|
@ -216,8 +219,8 @@ afterEach(() => {
|
|||
describe('#setup()', () => {
|
||||
describe('default', () => {
|
||||
it('passes legacy metadata from injectedVars to ui/metadata', () => {
|
||||
const legacyMetadata = { isLegacyMetadata: true };
|
||||
injectedMetadataSetup.getLegacyMetadata.mockReturnValue(legacyMetadata as any);
|
||||
const legacyMetadata = { nav: [], isLegacyMetadata: true };
|
||||
injectedMetadataSetup.getLegacyMetadata.mockReturnValueOnce(legacyMetadata as any);
|
||||
|
||||
const legacyPlatform = new LegacyPlatformService({
|
||||
...defaultParams,
|
||||
|
@ -404,7 +407,7 @@ describe('#start()', () => {
|
|||
legacyPlatform.start(defaultStartDeps);
|
||||
|
||||
expect(mockUICapabilitiesInit).toHaveBeenCalledTimes(1);
|
||||
expect(mockUICapabilitiesInit).toHaveBeenCalledWith(capabilitiesStart);
|
||||
expect(mockUICapabilitiesInit).toHaveBeenCalledWith(applicationStart.capabilities);
|
||||
});
|
||||
|
||||
describe('useLegacyTestHarness = false', () => {
|
||||
|
|
|
@ -52,8 +52,9 @@ export class LegacyPlatformService {
|
|||
|
||||
constructor(private readonly params: LegacyPlatformParams) {}
|
||||
|
||||
public async setup({ core }: SetupDeps) {
|
||||
public setup({ core }: SetupDeps) {
|
||||
const {
|
||||
application,
|
||||
i18n,
|
||||
injectedMetadata,
|
||||
fatalErrors,
|
||||
|
@ -81,6 +82,17 @@ export class LegacyPlatformService {
|
|||
require('ui/chrome/api/breadcrumbs').__newPlatformSetup__(chrome);
|
||||
require('ui/chrome/services/global_nav_state').__newPlatformSetup__(chrome);
|
||||
|
||||
injectedMetadata.getLegacyMetadata().nav.forEach((navLink: any) =>
|
||||
application.registerLegacyApp({
|
||||
id: navLink.id,
|
||||
order: navLink.order,
|
||||
title: navLink.title,
|
||||
euiIconType: navLink.euiIconType,
|
||||
icon: navLink.icon,
|
||||
appUrl: navLink.url,
|
||||
})
|
||||
);
|
||||
|
||||
// Load the bootstrap module before loading the legacy platform files so that
|
||||
// the bootstrap module can modify the environment a bit first
|
||||
this.bootstrapModule = this.loadBootstrapModule();
|
||||
|
@ -97,7 +109,7 @@ export class LegacyPlatformService {
|
|||
this.targetDomElement = targetDomElement;
|
||||
|
||||
require('ui/new_platform').__newPlatformStart__(core);
|
||||
require('ui/capabilities').__newPlatformStart__(core.capabilities);
|
||||
require('ui/capabilities').__newPlatformStart__(core.application.capabilities);
|
||||
|
||||
this.bootstrapModule.bootstrap(this.targetDomElement);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { DiscoveredPlugin } from '../../server';
|
||||
import { BasePathSetup } from '../base_path';
|
||||
import { BasePathSetup, BasePathStart } from '../base_path';
|
||||
import { ChromeSetup } from '../chrome';
|
||||
import { CoreContext } from '../core_system';
|
||||
import { FatalErrorsSetup } from '../fatal_errors';
|
||||
|
@ -27,8 +27,9 @@ import { NotificationsSetup, NotificationsStart } from '../notifications';
|
|||
import { UiSettingsSetup } from '../ui_settings';
|
||||
import { PluginWrapper } from './plugin';
|
||||
import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service';
|
||||
import { CapabilitiesStart } from '../capabilities';
|
||||
import { OverlayStart } from '../overlays';
|
||||
import { ApplicationStart } from '../application';
|
||||
import { HttpSetup } from '../http';
|
||||
|
||||
/**
|
||||
* The available core services passed to a `PluginInitializer`
|
||||
|
@ -47,6 +48,7 @@ export interface PluginSetupContext {
|
|||
basePath: BasePathSetup;
|
||||
chrome: ChromeSetup;
|
||||
fatalErrors: FatalErrorsSetup;
|
||||
http: HttpSetup;
|
||||
i18n: I18nSetup;
|
||||
notifications: NotificationsSetup;
|
||||
uiSettings: UiSettingsSetup;
|
||||
|
@ -58,7 +60,8 @@ export interface PluginSetupContext {
|
|||
* @public
|
||||
*/
|
||||
export interface PluginStartContext {
|
||||
capabilities: CapabilitiesStart;
|
||||
application: Pick<ApplicationStart, 'capabilities'>;
|
||||
basePath: BasePathStart;
|
||||
i18n: I18nStart;
|
||||
notifications: NotificationsStart;
|
||||
overlays: OverlayStart;
|
||||
|
@ -95,6 +98,7 @@ export function createPluginSetupContext<TSetup, TStart, TPluginsSetup, TPlugins
|
|||
plugin: PluginWrapper<TSetup, TStart, TPluginsSetup, TPluginsStart>
|
||||
): PluginSetupContext {
|
||||
return {
|
||||
http: deps.http,
|
||||
basePath: deps.basePath,
|
||||
chrome: deps.chrome,
|
||||
fatalErrors: deps.fatalErrors,
|
||||
|
@ -120,7 +124,10 @@ export function createPluginStartContext<TSetup, TStart, TPluginsSetup, TPlugins
|
|||
plugin: PluginWrapper<TSetup, TStart, TPluginsSetup, TPluginsStart>
|
||||
): PluginStartContext {
|
||||
return {
|
||||
capabilities: deps.capabilities,
|
||||
application: {
|
||||
capabilities: deps.application.capabilities,
|
||||
},
|
||||
basePath: deps.basePath,
|
||||
i18n: deps.i18n,
|
||||
notifications: deps.notifications,
|
||||
overlays: deps.overlays,
|
||||
|
|
|
@ -33,7 +33,7 @@ import {
|
|||
PluginsServiceSetupDeps,
|
||||
} from './plugins_service';
|
||||
import { notificationServiceMock } from '../notifications/notifications_service.mock';
|
||||
import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock';
|
||||
import { applicationServiceMock } from '../application/application_service.mock';
|
||||
import { i18nServiceMock } from '../i18n/i18n_service.mock';
|
||||
import { overlayServiceMock } from '../overlays/overlay_service.mock';
|
||||
import { PluginStartContext, PluginSetupContext } from './plugin_context';
|
||||
|
@ -42,6 +42,8 @@ import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.moc
|
|||
import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
|
||||
import { basePathServiceMock } from '../base_path/base_path_service.mock';
|
||||
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
|
||||
import { UiSettingsClient } from '../ui_settings';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
|
||||
export let mockPluginInitializers: Map<PluginName, MockedPluginInitializer>;
|
||||
|
||||
|
@ -59,6 +61,7 @@ let mockStartContext: DeeplyMocked<PluginStartContext>;
|
|||
|
||||
beforeEach(() => {
|
||||
mockSetupDeps = {
|
||||
application: applicationServiceMock.createSetupContract(),
|
||||
injectedMetadata: (function() {
|
||||
const metadata = injectedMetadataServiceMock.createSetupContract();
|
||||
metadata.getPlugins.mockReturnValue([
|
||||
|
@ -78,19 +81,26 @@ beforeEach(() => {
|
|||
})(),
|
||||
chrome: chromeServiceMock.createSetupContract(),
|
||||
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
i18n: i18nServiceMock.createSetupContract(),
|
||||
notifications: notificationServiceMock.createSetupContract(),
|
||||
uiSettings: uiSettingsServiceMock.createSetupContract(),
|
||||
} as any;
|
||||
mockSetupContext = omit(mockSetupDeps, 'injectedMetadata');
|
||||
uiSettings: uiSettingsServiceMock.createSetupContract() as jest.Mocked<UiSettingsClient>,
|
||||
};
|
||||
mockSetupContext = omit(mockSetupDeps, 'application', 'injectedMetadata');
|
||||
mockStartDeps = {
|
||||
capabilities: capabilitiesServiceMock.createStartContract(),
|
||||
application: applicationServiceMock.createStartContract(),
|
||||
basePath: basePathServiceMock.createStartContract(),
|
||||
i18n: i18nServiceMock.createStartContract(),
|
||||
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
|
||||
notifications: notificationServiceMock.createStartContract(),
|
||||
overlays: overlayServiceMock.createStartContract(),
|
||||
};
|
||||
mockStartContext = omit(mockStartDeps, 'injectedMetadata');
|
||||
mockStartContext = {
|
||||
...omit(mockStartDeps, 'injectedMetadata'),
|
||||
application: {
|
||||
capabilities: mockStartDeps.application.capabilities,
|
||||
},
|
||||
};
|
||||
|
||||
// Reset these for each test.
|
||||
mockPluginInitializers = new Map<PluginName, MockedPluginInitializer>(([
|
||||
|
|
|
@ -7,10 +7,35 @@
|
|||
import * as CSS from 'csstype';
|
||||
import { default } from 'react';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { Observable } from 'rxjs';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import * as Rx from 'rxjs';
|
||||
import { Toast } from '@elastic/eui';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ApplicationSetup {
|
||||
// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts
|
||||
registerApp(app: App): void;
|
||||
// Warning: (ae-forgotten-export) The symbol "LegacyApp" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @internal
|
||||
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;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface BasePathSetup {
|
||||
addToPath(path: string): string;
|
||||
|
@ -18,6 +43,9 @@ export interface BasePathSetup {
|
|||
removeFromPath(path: string): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type BasePathStart = BasePathSetup;
|
||||
|
||||
// @public
|
||||
export interface Capabilities {
|
||||
[key: string]: Record<string, boolean | Record<string, boolean>>;
|
||||
|
@ -28,11 +56,6 @@ export interface Capabilities {
|
|||
navLinks: Record<string, boolean>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface CapabilitiesStart {
|
||||
getCapabilities: () => Capabilities;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ChromeBadge {
|
||||
// (undocumented)
|
||||
|
@ -75,6 +98,8 @@ export interface CoreContext {
|
|||
|
||||
// @public
|
||||
export interface CoreSetup {
|
||||
// (undocumented)
|
||||
application: ApplicationSetup;
|
||||
// (undocumented)
|
||||
basePath: BasePathSetup;
|
||||
// (undocumented)
|
||||
|
@ -98,7 +123,9 @@ export interface CoreSetup {
|
|||
// @public (undocumented)
|
||||
export interface CoreStart {
|
||||
// (undocumented)
|
||||
capabilities: CapabilitiesStart;
|
||||
application: ApplicationStart;
|
||||
// (undocumented)
|
||||
basePath: BasePathStart;
|
||||
// (undocumented)
|
||||
i18n: I18nStart;
|
||||
// (undocumented)
|
||||
|
@ -175,7 +202,7 @@ export interface InjectedMetadataParams {
|
|||
app: unknown;
|
||||
translations: unknown;
|
||||
bundleId: string;
|
||||
nav: unknown;
|
||||
nav: LegacyNavLink[];
|
||||
version: string;
|
||||
branch: string;
|
||||
buildNum: number;
|
||||
|
@ -212,7 +239,7 @@ export interface InjectedMetadataSetup {
|
|||
app: unknown;
|
||||
translations: unknown;
|
||||
bundleId: string;
|
||||
nav: unknown;
|
||||
nav: LegacyNavLink[];
|
||||
version: string;
|
||||
branch: string;
|
||||
buildNum: number;
|
||||
|
@ -234,6 +261,22 @@ export interface InjectedMetadataSetup {
|
|||
// @public (undocumented)
|
||||
export type InjectedMetadataStart = InjectedMetadataSetup;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LegacyNavLink {
|
||||
// (undocumented)
|
||||
euiIconType?: string;
|
||||
// (undocumented)
|
||||
icon?: string;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
order: number;
|
||||
// (undocumented)
|
||||
title: string;
|
||||
// (undocumented)
|
||||
url: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface NotificationsSetup {
|
||||
// (undocumented)
|
||||
|
@ -282,6 +325,8 @@ export interface PluginSetupContext {
|
|||
// (undocumented)
|
||||
fatalErrors: FatalErrorsSetup;
|
||||
// (undocumented)
|
||||
http: HttpSetup;
|
||||
// (undocumented)
|
||||
i18n: I18nSetup;
|
||||
// (undocumented)
|
||||
notifications: NotificationsSetup;
|
||||
|
@ -356,8 +401,8 @@ export interface UiSettingsState {
|
|||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/core/public/injected_metadata/injected_metadata_service.ts:38:7 - (ae-forgotten-export) The symbol "PluginName" needs to be exported by the entry point index.d.ts
|
||||
// src/core/public/injected_metadata/injected_metadata_service.ts:39:7 - (ae-forgotten-export) The symbol "DiscoveredPlugin" needs to be exported by the entry point index.d.ts
|
||||
// src/core/public/injected_metadata/injected_metadata_service.ts:48:7 - (ae-forgotten-export) The symbol "PluginName" needs to be exported by the entry point index.d.ts
|
||||
// src/core/public/injected_metadata/injected_metadata_service.ts:49:7 - (ae-forgotten-export) The symbol "DiscoveredPlugin" needs to be exported by the entry point index.d.ts
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ type Freezable = { [k: string]: any } | any[];
|
|||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface RecursiveReadonlyArray<T> extends Array<RecursiveReadonly<T>> {}
|
||||
|
||||
type RecursiveReadonly<T> = T extends any[]
|
||||
export type RecursiveReadonly<T> = T extends any[]
|
||||
? RecursiveReadonlyArray<T[number]>
|
||||
: T extends object
|
||||
? Readonly<{ [K in keyof T]: RecursiveReadonly<T[K]> }>
|
||||
|
|
|
@ -96,6 +96,15 @@ export default function (kibana) {
|
|||
];
|
||||
},
|
||||
|
||||
uiCapabilities() {
|
||||
return {
|
||||
dev_tools: {
|
||||
show: true,
|
||||
save: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
async init(server, options) {
|
||||
server.expose('addExtensionSpecFilePath', addExtensionSpecFilePath);
|
||||
if (options.ssl && options.ssl.verify) {
|
||||
|
@ -111,12 +120,6 @@ export default function (kibana) {
|
|||
elasticsearchUrl: url.format(
|
||||
Object.assign(url.parse(head(legacyEsConfig.hosts)), { auth: false })
|
||||
),
|
||||
uiCapabilities: {
|
||||
dev_tools: {
|
||||
show: true,
|
||||
save: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
server.route(createProxyRoute({
|
||||
|
|
|
@ -228,62 +228,9 @@ export default function (kibana) {
|
|||
},
|
||||
|
||||
injectDefaultVars(server, options) {
|
||||
const { savedObjects } = server;
|
||||
|
||||
return {
|
||||
kbnIndex: options.index,
|
||||
kbnBaseUrl,
|
||||
uiCapabilities: {
|
||||
discover: {
|
||||
show: true,
|
||||
createShortUrl: true,
|
||||
save: true,
|
||||
},
|
||||
visualize: {
|
||||
show: true,
|
||||
createShortUrl: true,
|
||||
delete: true,
|
||||
save: true,
|
||||
},
|
||||
dashboard: {
|
||||
createNew: true,
|
||||
show: true,
|
||||
showWriteControls: true,
|
||||
},
|
||||
catalogue: {
|
||||
discover: true,
|
||||
dashboard: true,
|
||||
visualize: true,
|
||||
console: true,
|
||||
advanced_settings: true,
|
||||
index_patterns: true,
|
||||
},
|
||||
advancedSettings: {
|
||||
show: true,
|
||||
save: true
|
||||
},
|
||||
indexPatterns: {
|
||||
save: true,
|
||||
},
|
||||
savedObjectsManagement: savedObjects.types.reduce((acc, type) => ({
|
||||
...acc,
|
||||
[type]: {
|
||||
delete: true,
|
||||
edit: true,
|
||||
read: true,
|
||||
}
|
||||
}), {}),
|
||||
management: {
|
||||
/*
|
||||
* Management settings correspond to management section/link ids, and should not be changed
|
||||
* without also updating those definitions.
|
||||
*/
|
||||
kibana: {
|
||||
settings: true,
|
||||
index_patterns: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -293,6 +240,62 @@ export default function (kibana) {
|
|||
migrations,
|
||||
},
|
||||
|
||||
uiCapabilities: async function (server) {
|
||||
const { savedObjects } = server;
|
||||
|
||||
return {
|
||||
discover: {
|
||||
show: true,
|
||||
createShortUrl: true,
|
||||
save: true,
|
||||
},
|
||||
visualize: {
|
||||
show: true,
|
||||
createShortUrl: true,
|
||||
delete: true,
|
||||
save: true,
|
||||
},
|
||||
dashboard: {
|
||||
createNew: true,
|
||||
show: true,
|
||||
showWriteControls: true,
|
||||
},
|
||||
catalogue: {
|
||||
discover: true,
|
||||
dashboard: true,
|
||||
visualize: true,
|
||||
console: true,
|
||||
advanced_settings: true,
|
||||
index_patterns: true,
|
||||
},
|
||||
advancedSettings: {
|
||||
show: true,
|
||||
save: true
|
||||
},
|
||||
indexPatterns: {
|
||||
save: true,
|
||||
},
|
||||
savedObjectsManagement: savedObjects.types.reduce((acc, type) => ({
|
||||
...acc,
|
||||
[type]: {
|
||||
delete: true,
|
||||
edit: true,
|
||||
read: true,
|
||||
}
|
||||
}), {}),
|
||||
management: {
|
||||
/*
|
||||
* Management settings correspond to management section/link ids, and should not be changed
|
||||
* without also updating those definitions.
|
||||
*/
|
||||
kibana: {
|
||||
settings: true,
|
||||
index_patterns: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
preInit: async function (server) {
|
||||
try {
|
||||
// Create the data directory (recursively, if the a parent dir doesn't exist).
|
||||
|
|
|
@ -28,6 +28,7 @@ import { render, unmountComponentAtNode } from 'react-dom';
|
|||
import { ObjectsTable } from './components/objects_table';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { get } from 'lodash';
|
||||
import { getNewPlatform } from 'ui/new_platform';
|
||||
|
||||
import { getIndexBreadcrumbs } from './breadcrumbs';
|
||||
|
||||
|
@ -39,10 +40,10 @@ function updateObjectsTable($scope, $injector) {
|
|||
const $http = $injector.get('$http');
|
||||
const kbnUrl = $injector.get('kbnUrl');
|
||||
const config = $injector.get('config');
|
||||
const uiCapabilites = chrome.getInjected('uiCapabilities');
|
||||
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
const services = savedObjectManagementRegistry.all().map(obj => $injector.get(obj.service));
|
||||
const uiCapabilites = getNewPlatform().start.core.application.capabilities;
|
||||
|
||||
$scope.$$postDigest(() => {
|
||||
const node = document.getElementById(REACT_OBJECTS_TABLE_DOM_ELEMENT_ID);
|
||||
|
|
|
@ -35,8 +35,47 @@ import 'custom-event-polyfill';
|
|||
import 'whatwg-fetch';
|
||||
import 'abortcontroller-polyfill';
|
||||
import 'childnode-remove-polyfill';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { CoreSystem } from '__kibanaCore__'
|
||||
import { CoreSystem } from '__kibanaCore__';
|
||||
|
||||
// Fake uiCapabilities returned to Core in browser tests
|
||||
const uiCapabilities = {
|
||||
navLinks: {
|
||||
myLink: true,
|
||||
notMyLink: true,
|
||||
},
|
||||
discover: {
|
||||
showWriteControls: true
|
||||
},
|
||||
visualize: {
|
||||
save: true
|
||||
},
|
||||
dashboard: {
|
||||
showWriteControls: true
|
||||
},
|
||||
timelion: {
|
||||
save: true
|
||||
},
|
||||
};
|
||||
|
||||
// Stub fetch for CoreSystem calls.
|
||||
const fetchStub = sinon.stub(window, 'fetch');
|
||||
fetchStub.callsFake((url, options) => {
|
||||
if (url !== '/api/capabilities') {
|
||||
console.warn('Stubbed window.fetch does not support this request.');
|
||||
return Promise.resolve(new window.Response('Resource not found', { status: 404 }));
|
||||
}
|
||||
|
||||
return Promise.resolve(
|
||||
new window.Response(
|
||||
JSON.stringify({ capabilities: uiCapabilities })),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// render the core system in a child of the body as the default children of the body
|
||||
// in the browser tests are needed for mocha and other test components to work
|
||||
|
@ -48,6 +87,7 @@ const coreSystem = new CoreSystem({
|
|||
version: '1.2.3',
|
||||
buildNumber: 1234,
|
||||
legacyMetadata: {
|
||||
nav: [],
|
||||
version: '1.2.3',
|
||||
buildNum: 1234,
|
||||
devMode: true,
|
||||
|
@ -85,24 +125,6 @@ const coreSystem = new CoreSystem({
|
|||
enabled: true,
|
||||
enableExternalUrls: true
|
||||
},
|
||||
uiCapabilities: {
|
||||
navLinks: {
|
||||
myLink: true,
|
||||
notMyLink: true,
|
||||
},
|
||||
discover: {
|
||||
showWriteControls: true
|
||||
},
|
||||
visualize: {
|
||||
save: true
|
||||
},
|
||||
dashboard: {
|
||||
showWriteControls: true
|
||||
},
|
||||
timelion: {
|
||||
save: true
|
||||
},
|
||||
},
|
||||
interpreterConfig: {
|
||||
enableInVisualize: true
|
||||
}
|
||||
|
|
|
@ -37,6 +37,14 @@ export default function (kibana) {
|
|||
}).default();
|
||||
},
|
||||
|
||||
uiCapabilities() {
|
||||
return {
|
||||
timelion: {
|
||||
save: true,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
uiExports: {
|
||||
app: {
|
||||
title: 'Timelion',
|
||||
|
@ -54,11 +62,6 @@ export default function (kibana) {
|
|||
injectDefaultVars(server) {
|
||||
return {
|
||||
timelionUiEnabled: server.config().get('timelion.ui.enabled'),
|
||||
uiCapabilities: {
|
||||
timelion: {
|
||||
save: true,
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
visTypes: [
|
||||
|
|
|
@ -56,6 +56,7 @@ export class PluginSpec {
|
|||
version,
|
||||
kibanaVersion,
|
||||
uiExports,
|
||||
uiCapabilities,
|
||||
publicDir,
|
||||
configPrefix,
|
||||
config,
|
||||
|
@ -74,6 +75,7 @@ export class PluginSpec {
|
|||
|
||||
this._publicDir = publicDir;
|
||||
this._uiExports = uiExports;
|
||||
this._uiCapabilities = uiCapabilities;
|
||||
|
||||
this._configPrefix = configPrefix;
|
||||
this._configSchemaProvider = config;
|
||||
|
@ -170,6 +172,10 @@ export class PluginSpec {
|
|||
return this._uiExports;
|
||||
}
|
||||
|
||||
getUiCapabilitiesProvider() {
|
||||
return this._uiCapabilities;
|
||||
}
|
||||
|
||||
getPreInitHandler() {
|
||||
return this._preInit;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { Server } from '../../server/kbn_server';
|
||||
import { Capabilities } from '../../../core/public';
|
||||
|
||||
export type InitPluginFunction = (server: Server) => void;
|
||||
export interface UiExports {
|
||||
|
@ -29,6 +30,7 @@ export interface PluginSpecOptions {
|
|||
require: string[];
|
||||
publicDir: string;
|
||||
uiExports?: UiExports;
|
||||
uiCapabilities?: Capabilities;
|
||||
init: InitPluginFunction;
|
||||
config: any;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 const mockRegisterCapabilitiesRoute = jest.fn();
|
||||
jest.mock('./capabilities_route', () => ({
|
||||
registerCapabilitiesRoute: mockRegisterCapabilitiesRoute,
|
||||
}));
|
88
src/legacy/server/capabilities/capabilities_mixin.test.ts
Normal file
88
src/legacy/server/capabilities/capabilities_mixin.test.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 { Server } from 'hapi';
|
||||
import KbnServer from '../kbn_server';
|
||||
import { mockRegisterCapabilitiesRoute } from './capabilities_mixin.test.mocks';
|
||||
|
||||
import { capabilitiesMixin } from './capabilities_mixin';
|
||||
|
||||
describe('capabilitiesMixin', () => {
|
||||
const getKbnServer = (pluginSpecs: any[] = []) => {
|
||||
return {
|
||||
afterPluginsInit: (callback: () => void) => callback(),
|
||||
pluginSpecs,
|
||||
} as KbnServer;
|
||||
};
|
||||
|
||||
let server: Server;
|
||||
beforeEach(() => {
|
||||
server = new Server();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockRegisterCapabilitiesRoute.mockClear();
|
||||
});
|
||||
|
||||
it('calls registerCapabilitiesRoute with merged uiCapabilitiesProviers', async () => {
|
||||
const kbnServer = getKbnServer([
|
||||
{
|
||||
getUiCapabilitiesProvider: () => () => ({
|
||||
app1: { read: true },
|
||||
management: { section1: { feature1: true } },
|
||||
}),
|
||||
},
|
||||
{
|
||||
getUiCapabilitiesProvider: () => () => ({
|
||||
app2: { write: true },
|
||||
catalogue: { feature3: true },
|
||||
management: { section2: { feature2: true } },
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
await capabilitiesMixin(kbnServer, server);
|
||||
|
||||
expect(mockRegisterCapabilitiesRoute).toHaveBeenCalledWith(
|
||||
server,
|
||||
{
|
||||
app1: { read: true },
|
||||
app2: { write: true },
|
||||
catalogue: { feature3: true },
|
||||
management: {
|
||||
section1: { feature1: true },
|
||||
section2: { feature2: true },
|
||||
},
|
||||
navLinks: {},
|
||||
},
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
it('exposes server#registerCapabilitiesModifier for providing modifiers to the route', async () => {
|
||||
const kbnServer = getKbnServer();
|
||||
await capabilitiesMixin(kbnServer, server);
|
||||
const mockModifier1 = jest.fn();
|
||||
const mockModifier2 = jest.fn();
|
||||
server.registerCapabilitiesModifier(mockModifier1);
|
||||
server.registerCapabilitiesModifier(mockModifier2);
|
||||
|
||||
expect(mockRegisterCapabilitiesRoute.mock.calls[0][2]).toEqual([mockModifier1, mockModifier2]);
|
||||
});
|
||||
});
|
53
src/legacy/server/capabilities/capabilities_mixin.ts
Normal file
53
src/legacy/server/capabilities/capabilities_mixin.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 { Server, Request } from 'hapi';
|
||||
|
||||
import { Capabilities } from '../../../core/public';
|
||||
import KbnServer from '../kbn_server';
|
||||
import { registerCapabilitiesRoute } from './capabilities_route';
|
||||
import { mergeCapabilities } from './merge_capabilities';
|
||||
|
||||
export type CapabilitiesModifier = (
|
||||
request: Request,
|
||||
uiCapabilities: Capabilities
|
||||
) => Capabilities | Promise<Capabilities>;
|
||||
|
||||
export async function capabilitiesMixin(kbnServer: KbnServer, server: Server) {
|
||||
const modifiers: CapabilitiesModifier[] = [];
|
||||
|
||||
server.decorate('server', 'registerCapabilitiesModifier', (provider: CapabilitiesModifier) => {
|
||||
modifiers.push(provider);
|
||||
});
|
||||
|
||||
// Some plugin capabilities are derived from data provided by other plugins,
|
||||
// so we need to wait until after all plugins have been init'd to fetch uiCapabilities.
|
||||
kbnServer.afterPluginsInit(async () => {
|
||||
const defaultCapabilities = mergeCapabilities(
|
||||
...(await Promise.all(
|
||||
kbnServer.pluginSpecs
|
||||
.map(spec => spec.getUiCapabilitiesProvider())
|
||||
.filter(provider => !!provider)
|
||||
.map(provider => provider(server))
|
||||
))
|
||||
);
|
||||
|
||||
registerCapabilitiesRoute(server, defaultCapabilities, modifiers);
|
||||
});
|
||||
}
|
137
src/legacy/server/capabilities/capabilities_route.test.ts
Normal file
137
src/legacy/server/capabilities/capabilities_route.test.ts
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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 { Server } from 'hapi';
|
||||
import { registerCapabilitiesRoute } from './capabilities_route';
|
||||
import { Capabilities } from '../../../core/public';
|
||||
|
||||
describe('capabilities api', () => {
|
||||
const defaultCapabilities = {
|
||||
catalogue: {
|
||||
feature1: true,
|
||||
feature2: true,
|
||||
},
|
||||
management: {
|
||||
section1: {
|
||||
read: true,
|
||||
},
|
||||
section2: {
|
||||
write: true,
|
||||
},
|
||||
},
|
||||
navLinks: {
|
||||
app1: true,
|
||||
app2: true,
|
||||
},
|
||||
myApp: {
|
||||
read: true,
|
||||
write: true,
|
||||
kioskMode: true,
|
||||
},
|
||||
} as Capabilities;
|
||||
|
||||
let server: Server;
|
||||
|
||||
beforeEach(() => {
|
||||
server = new Server();
|
||||
});
|
||||
|
||||
it('returns unmodified uiCapabilities if no modifiers are available', async () => {
|
||||
registerCapabilitiesRoute(server, defaultCapabilities, []);
|
||||
const resp = await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/capabilities',
|
||||
payload: { capabilities: {} },
|
||||
});
|
||||
expect(JSON.parse(resp.payload)).toEqual({
|
||||
capabilities: defaultCapabilities,
|
||||
});
|
||||
});
|
||||
|
||||
it('merges payload capabilities with defaultCapabilities', async () => {
|
||||
registerCapabilitiesRoute(server, defaultCapabilities, []);
|
||||
const resp = await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/capabilities',
|
||||
payload: { capabilities: { navLinks: { app3: true } } },
|
||||
});
|
||||
expect(JSON.parse(resp.payload)).toEqual({
|
||||
capabilities: {
|
||||
...defaultCapabilities,
|
||||
navLinks: {
|
||||
...defaultCapabilities.navLinks,
|
||||
app3: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a single provider to modify uiCapabilities', async () => {
|
||||
registerCapabilitiesRoute(server, defaultCapabilities, [
|
||||
(req, caps) => {
|
||||
caps.management.section2.write = false;
|
||||
caps.myApp.write = false;
|
||||
return caps;
|
||||
},
|
||||
]);
|
||||
const resp = await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/capabilities',
|
||||
payload: { capabilities: {} },
|
||||
});
|
||||
const results = JSON.parse(resp.payload);
|
||||
expect(results.capabilities.management.section2.write).toBe(false);
|
||||
expect(results.capabilities.myApp.write).toBe(false);
|
||||
});
|
||||
|
||||
it('allows multiple providers to modify uiCapabilities', async () => {
|
||||
registerCapabilitiesRoute(server, defaultCapabilities, [
|
||||
(req, caps) => {
|
||||
caps.management.section2.write = false;
|
||||
return caps;
|
||||
},
|
||||
(req, caps) => {
|
||||
caps.myApp.write = false;
|
||||
return caps;
|
||||
},
|
||||
]);
|
||||
const resp = await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/capabilities',
|
||||
payload: { capabilities: {} },
|
||||
});
|
||||
const results = JSON.parse(resp.payload);
|
||||
expect(results.capabilities.management.section2.write).toBe(false);
|
||||
expect(results.capabilities.myApp.write).toBe(false);
|
||||
});
|
||||
|
||||
it('returns an error if any providers fail', async () => {
|
||||
registerCapabilitiesRoute(server, defaultCapabilities, [
|
||||
(req, caps) => {
|
||||
throw new Error(`Couldn't fetch license`);
|
||||
},
|
||||
]);
|
||||
const resp = await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/capabilities',
|
||||
payload: { capabilities: {} },
|
||||
});
|
||||
expect(resp.statusCode).toBe(500);
|
||||
});
|
||||
});
|
55
src/legacy/server/capabilities/capabilities_route.ts
Normal file
55
src/legacy/server/capabilities/capabilities_route.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 Joi from 'joi';
|
||||
import { Server } from 'hapi';
|
||||
|
||||
import { CapabilitiesModifier } from '.';
|
||||
import { Capabilities } from '../../../core/public';
|
||||
import { mergeCapabilities } from './merge_capabilities';
|
||||
|
||||
export const registerCapabilitiesRoute = (
|
||||
server: Server,
|
||||
defaultCapabilities: Capabilities,
|
||||
modifiers: CapabilitiesModifier[]
|
||||
) => {
|
||||
server.route({
|
||||
path: '/api/capabilities',
|
||||
method: 'POST',
|
||||
options: {
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
capabilities: Joi.object().required(),
|
||||
}).required(),
|
||||
},
|
||||
},
|
||||
async handler(request) {
|
||||
let { capabilities } = request.payload as { capabilities: Capabilities };
|
||||
capabilities = mergeCapabilities({ ...defaultCapabilities }, capabilities);
|
||||
|
||||
for (const provider of modifiers) {
|
||||
capabilities = await provider(request, capabilities);
|
||||
}
|
||||
|
||||
return {
|
||||
capabilities,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
20
src/legacy/server/capabilities/index.ts
Normal file
20
src/legacy/server/capabilities/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { CapabilitiesModifier, capabilitiesMixin } from './capabilities_mixin';
|
39
src/legacy/server/capabilities/merge_capabilities.ts
Normal file
39
src/legacy/server/capabilities/merge_capabilities.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { Capabilities } from '../../../core/public';
|
||||
|
||||
export const mergeCapabilities = (...sources: Capabilities[]): Capabilities =>
|
||||
sources.reduce(
|
||||
(capabilities, source) => {
|
||||
Object.entries(source).forEach(([key, value]) => {
|
||||
capabilities[key] = {
|
||||
...value,
|
||||
...capabilities[key],
|
||||
};
|
||||
});
|
||||
|
||||
return capabilities;
|
||||
},
|
||||
{
|
||||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
} as Capabilities
|
||||
);
|
8
src/legacy/server/kbn_server.d.ts
vendored
8
src/legacy/server/kbn_server.d.ts
vendored
|
@ -30,6 +30,7 @@ import {
|
|||
import { ApmOssPlugin } from '../core_plugins/apm_oss';
|
||||
import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch';
|
||||
|
||||
import { CapabilitiesModifier } from './capabilities';
|
||||
import { IndexPatternsServiceFactory } from './index_patterns';
|
||||
import {
|
||||
SavedObjectsClient,
|
||||
|
@ -61,8 +62,13 @@ declare module 'hapi' {
|
|||
config: () => KibanaConfig;
|
||||
indexPatternsServiceFactory: IndexPatternsServiceFactory;
|
||||
savedObjects: SavedObjectsService;
|
||||
usage: { collectorSet: any };
|
||||
injectUiAppVars: (pluginName: string, getAppVars: () => { [key: string]: any }) => void;
|
||||
getHiddenUiAppById(appId: string): UiApp;
|
||||
registerCapabilitiesModifier: (provider: CapabilitiesModifier) => void;
|
||||
addScopedTutorialContextFactory: (
|
||||
scopedTutorialContextFactory: (...args: any[]) => any
|
||||
) => void;
|
||||
savedObjectsManagement(): SavedObjectsManagement;
|
||||
}
|
||||
|
||||
|
@ -103,6 +109,7 @@ export default class KbnServer {
|
|||
};
|
||||
public server: Server;
|
||||
public inject: Server['inject'];
|
||||
public pluginSpecs: any[];
|
||||
|
||||
constructor(settings: any, core: any);
|
||||
|
||||
|
@ -110,6 +117,7 @@ export default class KbnServer {
|
|||
public mixin(...fns: KbnMixinFunc[]): Promise<void>;
|
||||
public listen(): Promise<Server>;
|
||||
public close(): Promise<void>;
|
||||
public afterPluginsInit(callback: () => void): void;
|
||||
public applyLoggingConfiguration(settings: any): void;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import * as Plugins from './plugins';
|
|||
import { indexPatternsMixin } from './index_patterns';
|
||||
import { savedObjectsMixin } from './saved_objects';
|
||||
import { sampleDataMixin } from './sample_data';
|
||||
import { capabilitiesMixin } from './capabilities';
|
||||
import { urlShorteningMixin } from './url_shortening';
|
||||
import { serverExtensionsMixin } from './server_extensions';
|
||||
import { uiMixin } from '../ui';
|
||||
|
@ -114,6 +115,9 @@ export default class KbnServer {
|
|||
// setup saved object routes
|
||||
savedObjectsMixin,
|
||||
|
||||
// setup capabilities routes
|
||||
capabilitiesMixin,
|
||||
|
||||
// setup routes for installing/uninstalling sample data sets
|
||||
sampleDataMixin,
|
||||
|
||||
|
|
|
@ -17,18 +17,17 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Capabilities as UICapabilities, CapabilitiesStart } from '../../../../core/public';
|
||||
import { Capabilities as UICapabilities } from '../../../../core/public';
|
||||
|
||||
export { UICapabilities };
|
||||
let uiCapabilities: UICapabilities;
|
||||
|
||||
let uiCapabilities: UICapabilities = null!;
|
||||
|
||||
export function __newPlatformStart__(capabililitiesService: CapabilitiesStart) {
|
||||
export function __newPlatformStart__(capabilities: UICapabilities) {
|
||||
if (uiCapabilities) {
|
||||
throw new Error('ui/capabilities already initialized with new platform apis');
|
||||
}
|
||||
|
||||
uiCapabilities = capabililitiesService.getCapabilities();
|
||||
uiCapabilities = capabilities;
|
||||
}
|
||||
|
||||
export const capabilities = {
|
||||
|
|
|
@ -36,6 +36,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import { CoreSetup } from 'kibana/public';
|
||||
|
||||
import { fatalError } from 'ui/notify';
|
||||
import { capabilities } from 'ui/capabilities';
|
||||
// @ts-ignore
|
||||
import { modifyUrl } from 'ui/url';
|
||||
// @ts-ignore
|
||||
|
@ -65,6 +66,7 @@ export const configureAppAngularModule = (angularModule: IModule) => {
|
|||
.value('serverName', legacyMetadata.serverName)
|
||||
.value('sessionId', Date.now())
|
||||
.value('esUrl', getEsUrl(newPlatform))
|
||||
.value('uiCapabilities', capabilities.get())
|
||||
.config(setupCompileProvider(newPlatform))
|
||||
.config(setupLocationProvider(newPlatform))
|
||||
.config($setupXsrfRequestInterceptor(newPlatform))
|
||||
|
|
|
@ -39,19 +39,6 @@ export function uiRenderMixin(kbnServer, server, config) {
|
|||
);
|
||||
}
|
||||
|
||||
function getInitialDefaultInjectedVars() {
|
||||
const navLinkSpecs = server.getUiNavLinks();
|
||||
|
||||
return {
|
||||
uiCapabilities: {
|
||||
navLinks: navLinkSpecs.reduce((acc, navLinkSpec) => ({
|
||||
...acc,
|
||||
[navLinkSpec._id]: true
|
||||
}), {})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let defaultInjectedVars = {};
|
||||
kbnServer.afterPluginsInit(() => {
|
||||
const { defaultInjectedVarProviders = [] } = kbnServer.uiExports;
|
||||
|
@ -61,7 +48,7 @@ export function uiRenderMixin(kbnServer, server, config) {
|
|||
allDefaults,
|
||||
fn(kbnServer.server, pluginSpec.readConfigValue(kbnServer.config, []))
|
||||
)
|
||||
), getInitialDefaultInjectedVars());
|
||||
), {});
|
||||
});
|
||||
|
||||
// render all views from ./views
|
||||
|
|
|
@ -109,28 +109,6 @@ export const security = (kibana) => new kibana.Plugin({
|
|||
sessionTimeout: config.get('xpack.security.sessionTimeout'),
|
||||
enableSpaceAwarePrivileges: config.get('xpack.spaces.enabled'),
|
||||
};
|
||||
},
|
||||
replaceInjectedVars: async function (originalInjectedVars, request, server) {
|
||||
// if we have a license which doesn't enable security, or we're a legacy user
|
||||
// we shouldn't disable any ui capabilities
|
||||
const { authorization } = server.plugins.security;
|
||||
if (!authorization.mode.useRbacForRequest(request)) {
|
||||
return originalInjectedVars;
|
||||
}
|
||||
|
||||
const disableUICapabilites = disableUICapabilitesFactory(server, request);
|
||||
// if we're an anonymous route, we disable all ui capabilities
|
||||
if (request.route.settings.auth === false) {
|
||||
return {
|
||||
...originalInjectedVars,
|
||||
uiCapabilities: disableUICapabilites.all(originalInjectedVars.uiCapabilities)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...originalInjectedVars,
|
||||
uiCapabilities: await disableUICapabilites.usingPrivileges(originalInjectedVars.uiCapabilities)
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -242,5 +220,22 @@ export const security = (kibana) => new kibana.Plugin({
|
|||
}
|
||||
};
|
||||
});
|
||||
|
||||
server.registerCapabilitiesModifier((request, uiCapabilities) => {
|
||||
// if we have a license which doesn't enable security, or we're a legacy user
|
||||
// we shouldn't disable any ui capabilities
|
||||
const { authorization } = server.plugins.security;
|
||||
if (!authorization.mode.useRbacForRequest(request)) {
|
||||
return uiCapabilities;
|
||||
}
|
||||
|
||||
const disableUICapabilites = disableUICapabilitesFactory(server, request);
|
||||
// if we're an anonymous route, we disable all ui capabilities
|
||||
if (request.route.settings.auth === false) {
|
||||
return disableUICapabilites.all(uiCapabilities);
|
||||
}
|
||||
|
||||
return disableUICapabilites.usingPrivileges(uiCapabilities);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -51,7 +51,7 @@ export function disableUICapabilitesFactory(
|
|||
|
||||
throw new Error(`Expected value type of boolean or object, but found ${value}`);
|
||||
})
|
||||
);
|
||||
) as UICapabilities;
|
||||
};
|
||||
|
||||
const usingPrivileges = async (uiCapabilities: UICapabilities) => {
|
||||
|
@ -143,7 +143,7 @@ export function disableUICapabilitesFactory(
|
|||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}) as UICapabilities;
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
import JoiNamespace from 'joi';
|
||||
import { resolve } from 'path';
|
||||
import { Server } from 'hapi';
|
||||
|
||||
import { getConfigSchema, initServerWithKibana, KbnServer } from './server/kibana.index';
|
||||
import { getConfigSchema, initServerWithKibana } from './server/kibana.index';
|
||||
|
||||
const APP_ID = 'siem';
|
||||
export const APP_NAME = 'SIEM';
|
||||
|
@ -43,7 +44,7 @@ export function siem(kibana: any) {
|
|||
config(Joi: typeof JoiNamespace) {
|
||||
return getConfigSchema(Joi);
|
||||
},
|
||||
init(server: KbnServer) {
|
||||
init(server: Server) {
|
||||
initServerWithKibana(server);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,13 +14,9 @@ import { createLogger } from './utils/logger';
|
|||
|
||||
const APP_ID = 'siem';
|
||||
|
||||
export interface KbnServer extends Server {
|
||||
usage: unknown;
|
||||
}
|
||||
|
||||
export const amMocking = (): boolean => process.env.INGEST_MOCKS === 'true';
|
||||
|
||||
export const initServerWithKibana = (kbnServer: KbnServer) => {
|
||||
export const initServerWithKibana = (kbnServer: Server) => {
|
||||
// bind is so "this" binds correctly to the logger since hapi server does not auto-bind its methods
|
||||
const logger = createLogger(kbnServer.log.bind(kbnServer));
|
||||
logger.info('Plugin initializing');
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { resolve } from 'path';
|
||||
|
||||
import { SavedObjectsService } from 'src/legacy/server/saved_objects';
|
||||
import { Request, Server } from 'hapi';
|
||||
// @ts-ignore
|
||||
import { AuditLogger } from '../../server/lib/audit_logger';
|
||||
// @ts-ignore
|
||||
|
@ -43,6 +44,14 @@ export const spaces = (kibana: Record<string, any>) =>
|
|||
}).default();
|
||||
},
|
||||
|
||||
uiCapabilities() {
|
||||
return {
|
||||
spaces: {
|
||||
manage: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
uiExports: {
|
||||
chromeNavControls: ['plugins/spaces/views/nav_control'],
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
|
@ -74,11 +83,6 @@ export const spaces = (kibana: Record<string, any>) =>
|
|||
spaces: [],
|
||||
activeSpace: null,
|
||||
spaceSelectorURL: getSpaceSelectorUrl(server.config()),
|
||||
uiCapabilities: {
|
||||
spaces: {
|
||||
manage: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
async replaceInjectedVars(
|
||||
|
@ -103,20 +107,11 @@ export const spaces = (kibana: Record<string, any>) =>
|
|||
};
|
||||
}
|
||||
|
||||
if (vars.activeSpace.space) {
|
||||
const features = server.plugins.xpack_main.getFeatures();
|
||||
vars.uiCapabilities = toggleUICapabilities(
|
||||
features,
|
||||
vars.uiCapabilities,
|
||||
vars.activeSpace.space
|
||||
);
|
||||
}
|
||||
|
||||
return vars;
|
||||
},
|
||||
},
|
||||
|
||||
async init(server: any) {
|
||||
async init(server: Server) {
|
||||
const thisPlugin = this;
|
||||
const xpackMainPlugin = server.plugins.xpack_main;
|
||||
|
||||
|
@ -140,10 +135,10 @@ export const spaces = (kibana: Record<string, any>) =>
|
|||
);
|
||||
|
||||
server.expose('spacesClient', {
|
||||
getScopedClient: (request: Record<string, any>) => {
|
||||
getScopedClient: (request: Request) => {
|
||||
const adminCluster = server.plugins.elasticsearch.getCluster('admin');
|
||||
const { callWithRequest, callWithInternalUser } = adminCluster;
|
||||
const callCluster = (...args: any[]) => callWithRequest(request, ...args);
|
||||
const callCluster = callWithRequest.bind(adminCluster, request);
|
||||
const { savedObjects } = server;
|
||||
const internalRepository = savedObjects.getSavedObjectsRepository(callWithInternalUser);
|
||||
const callWithRequestRepository = savedObjects.getSavedObjectsRepository(callCluster);
|
||||
|
@ -182,5 +177,21 @@ export const spaces = (kibana: Record<string, any>) =>
|
|||
|
||||
// Register a function with server to manage the collection of usage stats
|
||||
server.usage.collectorSet.register(getSpacesUsageCollector(server));
|
||||
|
||||
server.registerCapabilitiesModifier(async (request, uiCapabilities) => {
|
||||
const spacesClient = server.plugins.spaces.spacesClient.getScopedClient(request);
|
||||
try {
|
||||
const activeSpace = await getActiveSpace(
|
||||
spacesClient,
|
||||
request.getBasePath(),
|
||||
server.config().get('server.basePath')
|
||||
);
|
||||
|
||||
const features = server.plugins.xpack_main.getFeatures();
|
||||
return toggleUICapabilities(features, uiCapabilities, activeSpace);
|
||||
} catch (e) {
|
||||
return uiCapabilities;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status';
|
|||
import { replaceInjectedVars } from './server/lib/replace_injected_vars';
|
||||
import { setupXPackMain } from './server/lib/setup_xpack_main';
|
||||
import { getLocalizationUsageCollector } from './server/lib/get_localization_usage_collector';
|
||||
import { uiCapabilitiesForFeatures } from './server/lib/ui_capabilities_for_features';
|
||||
import {
|
||||
xpackInfoRoute,
|
||||
telemetryRoute,
|
||||
|
@ -61,6 +62,10 @@ export const xpackMain = (kibana) => {
|
|||
}).default();
|
||||
},
|
||||
|
||||
uiCapabilities(server) {
|
||||
return uiCapabilitiesForFeatures(server.plugins.xpack_main);
|
||||
},
|
||||
|
||||
uiExports: {
|
||||
managementSections: ['plugins/xpack_main/views/management'],
|
||||
uiSettingDefaults: {
|
||||
|
@ -92,6 +97,7 @@ export const xpackMain = (kibana) => {
|
|||
},
|
||||
injectDefaultVars(server) {
|
||||
const config = server.config();
|
||||
|
||||
return {
|
||||
telemetryUrl: config.get('xpack.xpack_main.telemetry.url'),
|
||||
telemetryEnabled: isTelemetryEnabled(config),
|
||||
|
|
|
@ -47,12 +47,6 @@ describe('replaceInjectedVars uiExport', () => {
|
|||
xpackInitialInfo: {
|
||||
b: 1
|
||||
},
|
||||
uiCapabilities: {
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {}
|
||||
},
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(server.plugins.security.isAuthenticated);
|
||||
|
@ -72,12 +66,6 @@ describe('replaceInjectedVars uiExport', () => {
|
|||
xpackInitialInfo: {
|
||||
b: 1
|
||||
},
|
||||
uiCapabilities: {
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -94,12 +82,6 @@ describe('replaceInjectedVars uiExport', () => {
|
|||
xpackInitialInfo: {
|
||||
b: 1
|
||||
},
|
||||
uiCapabilities: {
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -116,12 +98,6 @@ describe('replaceInjectedVars uiExport', () => {
|
|||
xpackInitialInfo: {
|
||||
b: 1
|
||||
},
|
||||
uiCapabilities: {
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -138,12 +114,6 @@ describe('replaceInjectedVars uiExport', () => {
|
|||
xpackInitialInfo: {
|
||||
b: 1
|
||||
},
|
||||
uiCapabilities: {
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -160,49 +130,27 @@ describe('replaceInjectedVars uiExport', () => {
|
|||
xpackInitialInfo: {
|
||||
b: 1
|
||||
},
|
||||
uiCapabilities: {
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sends the originalInjectedVars augmented with UI Capabilities if not authenticated', async () => {
|
||||
it('sends the originalInjectedVars if not authenticated', async () => {
|
||||
const originalInjectedVars = { a: 1 };
|
||||
const request = buildRequest();
|
||||
const server = mockServer();
|
||||
server.plugins.security.isAuthenticated.returns(false);
|
||||
|
||||
const newVars = await replaceInjectedVars(originalInjectedVars, request, server);
|
||||
expect(newVars).to.eql({
|
||||
...originalInjectedVars,
|
||||
uiCapabilities: {
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {}
|
||||
},
|
||||
});
|
||||
expect(newVars).to.eql(originalInjectedVars);
|
||||
});
|
||||
|
||||
it('sends the originalInjectedVars augmented with UI Capabilities if xpack info is unavailable', async () => {
|
||||
it('sends the originalInjectedVars if xpack info is unavailable', async () => {
|
||||
const originalInjectedVars = { a: 1 };
|
||||
const request = buildRequest();
|
||||
const server = mockServer();
|
||||
server.plugins.xpack_main.info.isAvailable.returns(false);
|
||||
|
||||
const newVars = await replaceInjectedVars(originalInjectedVars, request, server);
|
||||
expect(newVars).to.eql({
|
||||
...originalInjectedVars,
|
||||
uiCapabilities: {
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {}
|
||||
},
|
||||
});
|
||||
expect(newVars).to.eql(originalInjectedVars);
|
||||
});
|
||||
|
||||
it('sends the originalInjectedVars (with xpackInitialInfo = undefined) if security is disabled, xpack info is unavailable', async () => {
|
||||
|
@ -220,9 +168,6 @@ describe('replaceInjectedVars uiExport', () => {
|
|||
uiCapabilities: {
|
||||
navLinks: { foo: true },
|
||||
bar: { baz: true },
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {
|
||||
cfoo: true,
|
||||
}
|
||||
|
@ -230,22 +175,14 @@ describe('replaceInjectedVars uiExport', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('sends the originalInjectedVars augmented with UI Capabilities if the license check result is not available', async () => {
|
||||
it('sends the originalInjectedVars if the license check result is not available', async () => {
|
||||
const originalInjectedVars = { a: 1 };
|
||||
const request = buildRequest();
|
||||
const server = mockServer();
|
||||
server.plugins.xpack_main.info.feature().getLicenseCheckResults.returns(undefined);
|
||||
|
||||
const newVars = await replaceInjectedVars(originalInjectedVars, request, server);
|
||||
expect(newVars).to.eql({
|
||||
...originalInjectedVars,
|
||||
uiCapabilities: {
|
||||
mockFeature: {
|
||||
mockFeatureCapability: true,
|
||||
},
|
||||
catalogue: {}
|
||||
},
|
||||
});
|
||||
expect(newVars).to.eql(originalInjectedVars);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -5,20 +5,12 @@
|
|||
*/
|
||||
|
||||
import { getTelemetryOptIn } from './get_telemetry_opt_in';
|
||||
import { populateUICapabilities } from './populate_ui_capabilities';
|
||||
|
||||
export async function replaceInjectedVars(originalInjectedVars, request, server) {
|
||||
const xpackInfo = server.plugins.xpack_main.info;
|
||||
|
||||
const originalInjectedVarsWithUICapabilities = {
|
||||
...originalInjectedVars,
|
||||
uiCapabilities: {
|
||||
...populateUICapabilities(server.plugins.xpack_main, originalInjectedVars.uiCapabilities),
|
||||
}
|
||||
};
|
||||
|
||||
const withXpackInfo = async () => ({
|
||||
...originalInjectedVarsWithUICapabilities,
|
||||
...originalInjectedVars,
|
||||
telemetryOptedIn: await getTelemetryOptIn(request),
|
||||
xpackInitialInfo: xpackInfo.isAvailable() ? xpackInfo.toJSON() : undefined,
|
||||
});
|
||||
|
@ -30,12 +22,12 @@ export async function replaceInjectedVars(originalInjectedVars, request, server)
|
|||
|
||||
// not enough license info to make decision one way or another
|
||||
if (!xpackInfo.isAvailable() || !xpackInfo.feature('security').getLicenseCheckResults()) {
|
||||
return originalInjectedVarsWithUICapabilities;
|
||||
return originalInjectedVars;
|
||||
}
|
||||
|
||||
// request is not authenticated
|
||||
if (!await server.plugins.security.isAuthenticated(request)) {
|
||||
return originalInjectedVarsWithUICapabilities;
|
||||
return originalInjectedVars;
|
||||
}
|
||||
|
||||
// plugin enabled, license is appropriate, request is authenticated
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { UICapabilities } from 'ui/capabilities';
|
||||
import { Feature } from './feature_registry';
|
||||
import { populateUICapabilities } from './populate_ui_capabilities';
|
||||
import { uiCapabilitiesForFeatures } from './ui_capabilities_for_features';
|
||||
|
||||
function getMockXpackMainPlugin(features: Feature[]) {
|
||||
return {
|
||||
|
@ -14,26 +13,6 @@ function getMockXpackMainPlugin(features: Feature[]) {
|
|||
};
|
||||
}
|
||||
|
||||
function getMockOriginalInjectedVars() {
|
||||
return {
|
||||
uiCapabilities: {
|
||||
navLinks: {
|
||||
foo: true,
|
||||
bar: true,
|
||||
},
|
||||
management: {},
|
||||
catalogue: {
|
||||
fooEntry: true,
|
||||
barEntry: true,
|
||||
},
|
||||
feature: {
|
||||
someCapability: true,
|
||||
},
|
||||
otherFeature: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createFeaturePrivilege(key: string, capabilities: string[] = []) {
|
||||
return {
|
||||
[key]: {
|
||||
|
@ -51,28 +30,7 @@ describe('populateUICapabilities', () => {
|
|||
it('handles no original uiCapabilites and no registered features gracefully', () => {
|
||||
const xpackMainPlugin = getMockXpackMainPlugin([]);
|
||||
|
||||
expect(populateUICapabilities(xpackMainPlugin, {} as UICapabilities)).toEqual({});
|
||||
});
|
||||
|
||||
it('returns the original uiCapabilities untouched when no features are registered', () => {
|
||||
const xpackMainPlugin = getMockXpackMainPlugin([]);
|
||||
const originalInjectedVars = getMockOriginalInjectedVars();
|
||||
|
||||
expect(populateUICapabilities(xpackMainPlugin, originalInjectedVars.uiCapabilities)).toEqual({
|
||||
feature: {
|
||||
someCapability: true,
|
||||
},
|
||||
navLinks: {
|
||||
foo: true,
|
||||
bar: true,
|
||||
},
|
||||
management: {},
|
||||
catalogue: {
|
||||
fooEntry: true,
|
||||
barEntry: true,
|
||||
},
|
||||
otherFeature: {},
|
||||
});
|
||||
expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({});
|
||||
});
|
||||
|
||||
it('handles features with no registered capabilities', () => {
|
||||
|
@ -86,23 +44,10 @@ describe('populateUICapabilities', () => {
|
|||
},
|
||||
},
|
||||
]);
|
||||
const originalInjectedVars = getMockOriginalInjectedVars();
|
||||
|
||||
expect(populateUICapabilities(xpackMainPlugin, originalInjectedVars.uiCapabilities)).toEqual({
|
||||
feature: {
|
||||
someCapability: true,
|
||||
},
|
||||
navLinks: {
|
||||
foo: true,
|
||||
bar: true,
|
||||
},
|
||||
management: {},
|
||||
catalogue: {
|
||||
fooEntry: true,
|
||||
barEntry: true,
|
||||
},
|
||||
expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({
|
||||
catalogue: {},
|
||||
newFeature: {},
|
||||
otherFeature: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -118,26 +63,13 @@ describe('populateUICapabilities', () => {
|
|||
},
|
||||
},
|
||||
]);
|
||||
const originalInjectedVars = getMockOriginalInjectedVars();
|
||||
|
||||
expect(populateUICapabilities(xpackMainPlugin, originalInjectedVars.uiCapabilities)).toEqual({
|
||||
feature: {
|
||||
someCapability: true,
|
||||
},
|
||||
navLinks: {
|
||||
foo: true,
|
||||
bar: true,
|
||||
},
|
||||
management: {},
|
||||
catalogue: {
|
||||
fooEntry: true,
|
||||
barEntry: true,
|
||||
},
|
||||
expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({
|
||||
catalogue: {},
|
||||
newFeature: {
|
||||
capability1: true,
|
||||
capability2: true,
|
||||
},
|
||||
otherFeature: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -156,21 +88,10 @@ describe('populateUICapabilities', () => {
|
|||
},
|
||||
},
|
||||
]);
|
||||
const originalInjectedVars = getMockOriginalInjectedVars();
|
||||
|
||||
expect(populateUICapabilities(xpackMainPlugin, originalInjectedVars.uiCapabilities)).toEqual({
|
||||
feature: {
|
||||
someCapability: true,
|
||||
},
|
||||
navLinks: {
|
||||
foo: true,
|
||||
bar: true,
|
||||
},
|
||||
management: {},
|
||||
expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({
|
||||
catalogue: {
|
||||
fooEntry: true,
|
||||
anotherFooEntry: true,
|
||||
barEntry: true,
|
||||
anotherBarEntry: true,
|
||||
},
|
||||
newFeature: {
|
||||
|
@ -179,7 +100,6 @@ describe('populateUICapabilities', () => {
|
|||
capability3: true,
|
||||
capability4: true,
|
||||
},
|
||||
otherFeature: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -197,21 +117,9 @@ describe('populateUICapabilities', () => {
|
|||
},
|
||||
},
|
||||
]);
|
||||
const originalInjectedVars = getMockOriginalInjectedVars();
|
||||
|
||||
expect(populateUICapabilities(xpackMainPlugin, originalInjectedVars.uiCapabilities)).toEqual({
|
||||
feature: {
|
||||
someCapability: true,
|
||||
},
|
||||
navLinks: {
|
||||
foo: true,
|
||||
bar: true,
|
||||
},
|
||||
management: {},
|
||||
catalogue: {
|
||||
fooEntry: true,
|
||||
barEntry: true,
|
||||
},
|
||||
expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({
|
||||
catalogue: {},
|
||||
newFeature: {
|
||||
capability1: true,
|
||||
capability2: true,
|
||||
|
@ -219,7 +127,6 @@ describe('populateUICapabilities', () => {
|
|||
capability4: true,
|
||||
capability5: true,
|
||||
},
|
||||
otherFeature: {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -261,27 +168,15 @@ describe('populateUICapabilities', () => {
|
|||
},
|
||||
},
|
||||
]);
|
||||
const originalInjectedVars = getMockOriginalInjectedVars();
|
||||
|
||||
expect(populateUICapabilities(xpackMainPlugin, originalInjectedVars.uiCapabilities)).toEqual({
|
||||
expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({
|
||||
anotherNewFeature: {
|
||||
capability1: true,
|
||||
capability2: true,
|
||||
capability3: true,
|
||||
capability4: true,
|
||||
},
|
||||
feature: {
|
||||
someCapability: true,
|
||||
},
|
||||
navLinks: {
|
||||
foo: true,
|
||||
bar: true,
|
||||
},
|
||||
management: {},
|
||||
catalogue: {
|
||||
fooEntry: true,
|
||||
barEntry: true,
|
||||
},
|
||||
catalogue: {},
|
||||
newFeature: {
|
||||
capability1: true,
|
||||
capability2: true,
|
||||
|
@ -289,7 +184,6 @@ describe('populateUICapabilities', () => {
|
|||
capability4: true,
|
||||
capability5: true,
|
||||
},
|
||||
otherFeature: {},
|
||||
yetAnotherNewFeature: {
|
||||
capability1: true,
|
||||
capability2: true,
|
|
@ -14,15 +14,12 @@ interface FeatureCapabilities {
|
|||
[featureId: string]: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export function populateUICapabilities(
|
||||
xpackMainPlugin: Record<string, any>,
|
||||
uiCapabilities: UICapabilities
|
||||
): UICapabilities {
|
||||
export function uiCapabilitiesForFeatures(xpackMainPlugin: Record<string, any>): UICapabilities {
|
||||
const features: Feature[] = xpackMainPlugin.getFeatures();
|
||||
|
||||
const featureCapabilities: FeatureCapabilities[] = features.map(getCapabilitiesFromFeature);
|
||||
|
||||
return mergeCapabilities(uiCapabilities || {}, ...featureCapabilities);
|
||||
return buildCapabilities(...featureCapabilities);
|
||||
}
|
||||
|
||||
function getCapabilitiesFromFeature(feature: Feature): FeatureCapabilities {
|
||||
|
@ -60,25 +57,28 @@ function getCapabilitiesFromFeature(feature: Feature): FeatureCapabilities {
|
|||
return UIFeatureCapabilities;
|
||||
}
|
||||
|
||||
function mergeCapabilities(
|
||||
originalCapabilities: UICapabilities,
|
||||
...allFeatureCapabilities: FeatureCapabilities[]
|
||||
): UICapabilities {
|
||||
return allFeatureCapabilities.reduce<UICapabilities>((acc, capabilities) => {
|
||||
const mergableCapabilities: UICapabilities = _.omit(capabilities, ...ELIGIBLE_FLAT_MERGE_KEYS);
|
||||
function buildCapabilities(...allFeatureCapabilities: FeatureCapabilities[]): UICapabilities {
|
||||
return allFeatureCapabilities.reduce<UICapabilities>(
|
||||
(acc, capabilities) => {
|
||||
const mergableCapabilities: UICapabilities = _.omit(
|
||||
capabilities,
|
||||
...ELIGIBLE_FLAT_MERGE_KEYS
|
||||
);
|
||||
|
||||
const mergedFeatureCapabilities = {
|
||||
...mergableCapabilities,
|
||||
...acc,
|
||||
};
|
||||
|
||||
ELIGIBLE_FLAT_MERGE_KEYS.forEach(key => {
|
||||
mergedFeatureCapabilities[key] = {
|
||||
...mergedFeatureCapabilities[key],
|
||||
...capabilities[key],
|
||||
const mergedFeatureCapabilities = {
|
||||
...mergableCapabilities,
|
||||
...acc,
|
||||
};
|
||||
});
|
||||
|
||||
return mergedFeatureCapabilities;
|
||||
}, originalCapabilities);
|
||||
ELIGIBLE_FLAT_MERGE_KEYS.forEach(key => {
|
||||
mergedFeatureCapabilities[key] = {
|
||||
...mergedFeatureCapabilities[key],
|
||||
...capabilities[key],
|
||||
};
|
||||
});
|
||||
|
||||
return mergedFeatureCapabilities;
|
||||
},
|
||||
{} as UICapabilities
|
||||
);
|
||||
}
|
|
@ -50,6 +50,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
...xPackFunctionalTestsConfig.get('kbnTestServer.serverArgs'),
|
||||
...disabledPlugins.map(key => `--xpack.${key}.enabled=false`),
|
||||
`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'foo_plugin')}`,
|
||||
'--optimize.enabled=false',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
interface NestedBooleanObject {
|
||||
[key: string]: boolean | NestedBooleanObject;
|
||||
}
|
||||
|
||||
export const assertDeeplyFalse = (obj: NestedBooleanObject, path: string[] = []) => {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key];
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
assertDeeplyFalse(value, [...path, key]);
|
||||
} else if (typeof value === 'boolean') {
|
||||
if (value) {
|
||||
throw new Error(`${[...path, key].join('.')} is not false: ${value}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Expected nest object with boolean keys. '${key}' is not boolean: ${value}.`);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import cheerio from 'cheerio';
|
||||
import { UICapabilities } from 'ui/capabilities';
|
||||
import { format as formatUrl } from 'url';
|
||||
import util from 'util';
|
||||
|
@ -41,22 +40,23 @@ export class UICapabilitiesService {
|
|||
});
|
||||
}
|
||||
|
||||
public async get(
|
||||
credentials: BasicCredentials | null,
|
||||
spaceId?: string
|
||||
): Promise<GetUICapabilitiesResult> {
|
||||
public async get({
|
||||
credentials,
|
||||
navLinks,
|
||||
spaceId,
|
||||
}: {
|
||||
credentials?: BasicCredentials;
|
||||
navLinks?: Record<string, boolean>;
|
||||
spaceId?: string;
|
||||
}): Promise<GetUICapabilitiesResult> {
|
||||
const spaceUrlPrefix = spaceId ? `/s/${spaceId}` : '';
|
||||
this.log.debug(`requesting ${spaceUrlPrefix}/app/kibana to parse the uiCapabilities`);
|
||||
const requestHeaders = credentials
|
||||
? {
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
`${credentials.username}:${credentials.password}`
|
||||
).toString('base64')}`,
|
||||
}
|
||||
: {};
|
||||
const response = await this.axios.get(`${spaceUrlPrefix}/app/kibana`, {
|
||||
headers: requestHeaders,
|
||||
});
|
||||
this.log.debug(`requesting ${spaceUrlPrefix}/api/capabilities to get the uiCapabilities`);
|
||||
const requestOptions = credentials ? { auth: credentials } : {};
|
||||
const response = await this.axios.post(
|
||||
`${spaceUrlPrefix}/api/capabilities`,
|
||||
{ capabilities: { navLinks } },
|
||||
requestOptions
|
||||
);
|
||||
|
||||
if (response.status === 302 && response.headers.location === '/') {
|
||||
return {
|
||||
|
@ -65,13 +65,6 @@ export class UICapabilitiesService {
|
|||
};
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
return {
|
||||
success: false,
|
||||
failureReason: GetUICapabilitiesFailureReason.NotFound,
|
||||
};
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(
|
||||
`Expected status code of 200, received ${response.status} ${
|
||||
|
@ -80,25 +73,10 @@ export class UICapabilitiesService {
|
|||
);
|
||||
}
|
||||
|
||||
const dom = cheerio.load(response.data.toString());
|
||||
const element = dom('kbn-injected-metadata');
|
||||
if (!element) {
|
||||
throw new Error('Unable to find "kbn-injected-metadata" element ');
|
||||
}
|
||||
|
||||
const dataAttrJson = element.attr('data');
|
||||
|
||||
try {
|
||||
const dataAttr = JSON.parse(dataAttrJson);
|
||||
return {
|
||||
success: true,
|
||||
value: dataAttr.vars.uiCapabilities as UICapabilities,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Unable to parse JSON from the kbn-injected-metadata data attribute: ${dataAttrJson}`
|
||||
);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
value: response.data.capabilities,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,11 +7,9 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { mapValues } from 'lodash';
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import {
|
||||
GetUICapabilitiesFailureReason,
|
||||
UICapabilitiesService,
|
||||
} from '../../common/services/ui_capabilities';
|
||||
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
|
||||
import { UserAtSpaceScenarios } from '../scenarios';
|
||||
import { assertDeeplyFalse } from '../../common/lib/assert_deeply_false';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function catalogueTests({ getService }: KibanaFunctionalTestDefaultProviders) {
|
||||
|
@ -22,14 +20,16 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau
|
|||
it(`${scenario.id}`, async () => {
|
||||
const { user, space } = scenario;
|
||||
|
||||
const uiCapabilities = await uiCapabilitiesService.get(
|
||||
{ username: user.username, password: user.password },
|
||||
space.id
|
||||
);
|
||||
const uiCapabilities = await uiCapabilitiesService.get({
|
||||
credentials: { username: user.username, password: user.password },
|
||||
spaceId: space.id,
|
||||
});
|
||||
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('catalogue');
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'superuser at everything_space': {
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('catalogue');
|
||||
// everything is enabled
|
||||
const expected = mapValues(uiCapabilities.value!.catalogue, () => true);
|
||||
expect(uiCapabilities.value!.catalogue).to.eql(expected);
|
||||
|
@ -41,8 +41,6 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau
|
|||
case 'global_read at everything_space':
|
||||
case 'dual_privileges_read at everything_space':
|
||||
case 'everything_space_read at everything_space': {
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('catalogue');
|
||||
// everything except ml and monitoring is enabled
|
||||
const expected = mapValues(
|
||||
uiCapabilities.value!.catalogue,
|
||||
|
@ -60,16 +58,13 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau
|
|||
case 'dual_privileges_read at nothing_space':
|
||||
case 'nothing_space_all at nothing_space':
|
||||
case 'nothing_space_read at nothing_space': {
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('catalogue');
|
||||
// everything is disabled
|
||||
const expected = mapValues(uiCapabilities.value!.catalogue, () => false);
|
||||
expect(uiCapabilities.value!.catalogue).to.eql(expected);
|
||||
break;
|
||||
}
|
||||
// if we don't have access at the space itself, we're
|
||||
// redirected to the space selector and the ui capabilities
|
||||
// are lagely irrelevant because they won't be consumed
|
||||
// if we don't have access at the space itself, all ui
|
||||
// capabilities should be false
|
||||
case 'no_kibana_privileges at everything_space':
|
||||
case 'no_kibana_privileges at nothing_space':
|
||||
case 'legacy_all at everything_space':
|
||||
|
@ -78,10 +73,7 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau
|
|||
case 'everything_space_read at nothing_space':
|
||||
case 'nothing_space_all at everything_space':
|
||||
case 'nothing_space_read at everything_space':
|
||||
expect(uiCapabilities.success).to.be(false);
|
||||
expect(uiCapabilities.failureReason).to.be(
|
||||
GetUICapabilitiesFailureReason.RedirectedToRoot
|
||||
);
|
||||
assertDeeplyFalse(uiCapabilities.value!.catalogue);
|
||||
break;
|
||||
default:
|
||||
throw new UnreachableError(scenario);
|
||||
|
|
|
@ -6,11 +6,9 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import {
|
||||
GetUICapabilitiesFailureReason,
|
||||
UICapabilitiesService,
|
||||
} from '../../common/services/ui_capabilities';
|
||||
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
|
||||
import { UserAtSpaceScenarios } from '../scenarios';
|
||||
import { assertDeeplyFalse } from '../../common/lib/assert_deeply_false';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function fooTests({ getService }: KibanaFunctionalTestDefaultProviders) {
|
||||
|
@ -21,18 +19,18 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv
|
|||
it(`${scenario.id}`, async () => {
|
||||
const { user, space } = scenario;
|
||||
|
||||
const uiCapabilities = await uiCapabilitiesService.get(
|
||||
{ username: user.username, password: user.password },
|
||||
space.id
|
||||
);
|
||||
const uiCapabilities = await uiCapabilitiesService.get({
|
||||
credentials: { username: user.username, password: user.password },
|
||||
spaceId: space.id,
|
||||
});
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('foo');
|
||||
switch (scenario.id) {
|
||||
// these users have a read/write view
|
||||
case 'superuser at everything_space':
|
||||
case 'global_all at everything_space':
|
||||
case 'dual_privileges_all at everything_space':
|
||||
case 'everything_space_all at everything_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('foo');
|
||||
expect(uiCapabilities.value!.foo).to.eql({
|
||||
create: true,
|
||||
edit: true,
|
||||
|
@ -44,8 +42,6 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv
|
|||
case 'global_read at everything_space':
|
||||
case 'dual_privileges_read at everything_space':
|
||||
case 'everything_space_read at everything_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('foo');
|
||||
expect(uiCapabilities.value!.foo).to.eql({
|
||||
create: false,
|
||||
edit: false,
|
||||
|
@ -62,8 +58,6 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv
|
|||
case 'dual_privileges_read at nothing_space':
|
||||
case 'nothing_space_all at nothing_space':
|
||||
case 'nothing_space_read at nothing_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('foo');
|
||||
expect(uiCapabilities.value!.foo).to.eql({
|
||||
create: false,
|
||||
edit: false,
|
||||
|
@ -71,9 +65,8 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv
|
|||
show: false,
|
||||
});
|
||||
break;
|
||||
// if we don't have access at the space itself, we're
|
||||
// redirected to the space selector and the ui capabilities
|
||||
// are largely irrelevant because they won't be consumed
|
||||
// if we don't have access at the space itself, all ui
|
||||
// capabilities should be false
|
||||
case 'no_kibana_privileges at everything_space':
|
||||
case 'no_kibana_privileges at nothing_space':
|
||||
case 'legacy_all at everything_space':
|
||||
|
@ -82,10 +75,7 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv
|
|||
case 'everything_space_read at nothing_space':
|
||||
case 'nothing_space_all at everything_space':
|
||||
case 'nothing_space_read at everything_space':
|
||||
expect(uiCapabilities.success).to.be(false);
|
||||
expect(uiCapabilities.failureReason).to.be(
|
||||
GetUICapabilitiesFailureReason.RedirectedToRoot
|
||||
);
|
||||
assertDeeplyFalse(uiCapabilities.value!.foo);
|
||||
break;
|
||||
default:
|
||||
throw new UnreachableError(scenario);
|
||||
|
|
|
@ -8,10 +8,7 @@ import expect from '@kbn/expect';
|
|||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import { NavLinksBuilder } from '../../common/nav_links_builder';
|
||||
import { FeaturesService } from '../../common/services';
|
||||
import {
|
||||
GetUICapabilitiesFailureReason,
|
||||
UICapabilitiesService,
|
||||
} from '../../common/services/ui_capabilities';
|
||||
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
|
||||
import { UserAtSpaceScenarios } from '../scenarios';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@ -30,14 +27,15 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul
|
|||
it(`${scenario.id}`, async () => {
|
||||
const { user, space } = scenario;
|
||||
|
||||
const uiCapabilities = await uiCapabilitiesService.get(
|
||||
{ username: user.username, password: user.password },
|
||||
space.id
|
||||
);
|
||||
const uiCapabilities = await uiCapabilitiesService.get({
|
||||
credentials: { username: user.username, password: user.password },
|
||||
navLinks: navLinksBuilder.all(),
|
||||
spaceId: space.id,
|
||||
});
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
switch (scenario.id) {
|
||||
case 'superuser at everything_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.all());
|
||||
break;
|
||||
case 'global_all at everything_space':
|
||||
|
@ -46,8 +44,6 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul
|
|||
case 'global_read at everything_space':
|
||||
case 'everything_space_all at everything_space':
|
||||
case 'everything_space_read at everything_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(
|
||||
navLinksBuilder.except('ml', 'monitoring')
|
||||
);
|
||||
|
@ -59,10 +55,10 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul
|
|||
case 'global_read at nothing_space':
|
||||
case 'nothing_space_all at nothing_space':
|
||||
case 'nothing_space_read at nothing_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.only('management'));
|
||||
break;
|
||||
// these users have no access to any navLinks except management
|
||||
// which is not a navLink that ever gets disabled.
|
||||
case 'no_kibana_privileges at everything_space':
|
||||
case 'no_kibana_privileges at nothing_space':
|
||||
case 'legacy_all at everything_space':
|
||||
|
@ -71,10 +67,7 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul
|
|||
case 'everything_space_read at nothing_space':
|
||||
case 'nothing_space_all at everything_space':
|
||||
case 'nothing_space_read at everything_space':
|
||||
expect(uiCapabilities.success).to.be(false);
|
||||
expect(uiCapabilities.failureReason).to.be(
|
||||
GetUICapabilitiesFailureReason.RedirectedToRoot
|
||||
);
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.only('management'));
|
||||
break;
|
||||
default:
|
||||
throw new UnreachableError(scenario);
|
||||
|
|
|
@ -7,11 +7,9 @@ import expect from '@kbn/expect';
|
|||
import { mapValues } from 'lodash';
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import { SavedObjectsManagementBuilder } from '../../common/saved_objects_management_builder';
|
||||
import {
|
||||
GetUICapabilitiesFailureReason,
|
||||
UICapabilitiesService,
|
||||
} from '../../common/services/ui_capabilities';
|
||||
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
|
||||
import { UserAtSpaceScenarios } from '../scenarios';
|
||||
import { assertDeeplyFalse } from '../../common/lib/assert_deeply_false';
|
||||
|
||||
const savedObjectsManagementBuilder = new SavedObjectsManagementBuilder(true);
|
||||
|
||||
|
@ -26,15 +24,15 @@ export default function savedObjectsManagementTests({
|
|||
it(`${scenario.id}`, async () => {
|
||||
const { user, space } = scenario;
|
||||
|
||||
const uiCapabilities = await uiCapabilitiesService.get(
|
||||
{ username: user.username, password: user.password },
|
||||
space.id
|
||||
);
|
||||
const uiCapabilities = await uiCapabilitiesService.get({
|
||||
credentials: { username: user.username, password: user.password },
|
||||
spaceId: space.id,
|
||||
});
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
switch (scenario.id) {
|
||||
case 'superuser at everything_space':
|
||||
case 'superuser at nothing_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
const expected = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
|
||||
savedObjectsManagementBuilder.uiCapabilities('all')
|
||||
);
|
||||
|
@ -46,8 +44,6 @@ export default function savedObjectsManagementTests({
|
|||
case 'global_all at nothing_space':
|
||||
case 'dual_privileges_all at nothing_space':
|
||||
case 'nothing_space_all at nothing_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
|
||||
savedObjectsManagementBuilder.build({
|
||||
all: [
|
||||
|
@ -74,8 +70,6 @@ export default function savedObjectsManagementTests({
|
|||
case 'dual_privileges_read at nothing_space':
|
||||
case 'global_read at nothing_space':
|
||||
case 'nothing_space_read at nothing_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
|
||||
savedObjectsManagementBuilder.build({
|
||||
read: [
|
||||
|
@ -95,6 +89,8 @@ export default function savedObjectsManagementTests({
|
|||
})
|
||||
);
|
||||
break;
|
||||
// if we don't have access at the space itself, all ui
|
||||
// capabilities should be false
|
||||
case 'no_kibana_privileges at everything_space':
|
||||
case 'no_kibana_privileges at nothing_space':
|
||||
case 'legacy_all at everything_space':
|
||||
|
@ -103,10 +99,7 @@ export default function savedObjectsManagementTests({
|
|||
case 'everything_space_read at nothing_space':
|
||||
case 'nothing_space_all at everything_space':
|
||||
case 'nothing_space_read at everything_space':
|
||||
expect(uiCapabilities.success).to.be(false);
|
||||
expect(uiCapabilities.failureReason).to.be(
|
||||
GetUICapabilitiesFailureReason.RedirectedToRoot
|
||||
);
|
||||
assertDeeplyFalse(uiCapabilities.value!.savedObjectsManagement);
|
||||
break;
|
||||
default:
|
||||
throw new UnreachableError(scenario);
|
||||
|
|
|
@ -7,11 +7,9 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { mapValues } from 'lodash';
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import {
|
||||
GetUICapabilitiesFailureReason,
|
||||
UICapabilitiesService,
|
||||
} from '../../common/services/ui_capabilities';
|
||||
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
|
||||
import { UserScenarios } from '../scenarios';
|
||||
import { assertDeeplyFalse } from '../../common/lib/assert_deeply_false';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function catalogueTests({ getService }: KibanaFunctionalTestDefaultProviders) {
|
||||
|
@ -21,13 +19,15 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau
|
|||
UserScenarios.forEach(scenario => {
|
||||
it(`${scenario.fullName}`, async () => {
|
||||
const uiCapabilities = await uiCapabilitiesService.get({
|
||||
username: scenario.username,
|
||||
password: scenario.password,
|
||||
credentials: {
|
||||
username: scenario.username,
|
||||
password: scenario.password,
|
||||
},
|
||||
});
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('catalogue');
|
||||
switch (scenario.username) {
|
||||
case 'superuser': {
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('catalogue');
|
||||
// everything is enabled
|
||||
const expected = mapValues(uiCapabilities.value!.catalogue, () => true);
|
||||
expect(uiCapabilities.value!.catalogue).to.eql(expected);
|
||||
|
@ -37,8 +37,6 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau
|
|||
case 'read':
|
||||
case 'dual_privileges_all':
|
||||
case 'dual_privileges_read': {
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('catalogue');
|
||||
// everything except ml and monitoring is enabled
|
||||
const expected = mapValues(
|
||||
uiCapabilities.value!.catalogue,
|
||||
|
@ -49,8 +47,6 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau
|
|||
}
|
||||
case 'foo_all':
|
||||
case 'foo_read': {
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('catalogue');
|
||||
// only foo is enabled
|
||||
const expected = mapValues(
|
||||
uiCapabilities.value!.catalogue,
|
||||
|
@ -59,11 +55,10 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau
|
|||
expect(uiCapabilities.value!.catalogue).to.eql(expected);
|
||||
break;
|
||||
}
|
||||
// these users have no access to even get the ui capabilities
|
||||
// these users have no access to any ui capabilities
|
||||
case 'legacy_all':
|
||||
case 'no_kibana_privileges':
|
||||
expect(uiCapabilities.success).to.be(false);
|
||||
expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound);
|
||||
assertDeeplyFalse(uiCapabilities.value!.catalogue);
|
||||
break;
|
||||
default:
|
||||
throw new UnreachableError(scenario);
|
||||
|
|
|
@ -6,11 +6,9 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import {
|
||||
GetUICapabilitiesFailureReason,
|
||||
UICapabilitiesService,
|
||||
} from '../../common/services/ui_capabilities';
|
||||
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
|
||||
import { UserScenarios } from '../scenarios';
|
||||
import { assertDeeplyFalse } from '../../common/lib/assert_deeply_false';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function fooTests({ getService }: KibanaFunctionalTestDefaultProviders) {
|
||||
|
@ -20,17 +18,21 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv
|
|||
UserScenarios.forEach(scenario => {
|
||||
it(`${scenario.fullName}`, async () => {
|
||||
const uiCapabilities = await uiCapabilitiesService.get({
|
||||
username: scenario.username,
|
||||
password: scenario.password,
|
||||
credentials: {
|
||||
username: scenario.username,
|
||||
password: scenario.password,
|
||||
},
|
||||
});
|
||||
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('foo');
|
||||
|
||||
switch (scenario.username) {
|
||||
// these users have a read/write view of Foo
|
||||
case 'superuser':
|
||||
case 'all':
|
||||
case 'dual_privileges_all':
|
||||
case 'foo_all':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('foo');
|
||||
expect(uiCapabilities.value!.foo).to.eql({
|
||||
create: true,
|
||||
edit: true,
|
||||
|
@ -42,8 +44,6 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv
|
|||
case 'read':
|
||||
case 'dual_privileges_read':
|
||||
case 'foo_read':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('foo');
|
||||
expect(uiCapabilities.value!.foo).to.eql({
|
||||
create: false,
|
||||
edit: false,
|
||||
|
@ -51,11 +51,10 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv
|
|||
show: true,
|
||||
});
|
||||
break;
|
||||
// these users have no access to even get the ui capabilities
|
||||
// these users have no access to any ui capabilities
|
||||
case 'legacy_all':
|
||||
case 'no_kibana_privileges':
|
||||
expect(uiCapabilities.success).to.be(false);
|
||||
expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound);
|
||||
assertDeeplyFalse(uiCapabilities.value!.foo);
|
||||
break;
|
||||
// all other users can't do anything with Foo
|
||||
default:
|
||||
|
|
|
@ -8,10 +8,7 @@ import expect from '@kbn/expect';
|
|||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import { NavLinksBuilder } from '../../common/nav_links_builder';
|
||||
import { FeaturesService } from '../../common/services';
|
||||
import {
|
||||
GetUICapabilitiesFailureReason,
|
||||
UICapabilitiesService,
|
||||
} from '../../common/services/ui_capabilities';
|
||||
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
|
||||
import { UserScenarios } from '../scenarios';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@ -29,37 +26,38 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul
|
|||
UserScenarios.forEach(scenario => {
|
||||
it(`${scenario.fullName}`, async () => {
|
||||
const uiCapabilities = await uiCapabilitiesService.get({
|
||||
username: scenario.username,
|
||||
password: scenario.password,
|
||||
credentials: {
|
||||
username: scenario.username,
|
||||
password: scenario.password,
|
||||
},
|
||||
navLinks: navLinksBuilder.all(),
|
||||
});
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
|
||||
switch (scenario.username) {
|
||||
case 'superuser':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.all());
|
||||
break;
|
||||
case 'all':
|
||||
case 'read':
|
||||
case 'dual_privileges_all':
|
||||
case 'dual_privileges_read':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(
|
||||
navLinksBuilder.except('ml', 'monitoring')
|
||||
);
|
||||
break;
|
||||
case 'foo_all':
|
||||
case 'foo_read':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(
|
||||
navLinksBuilder.only('management', 'foo')
|
||||
);
|
||||
break;
|
||||
// these users have no access to any navLinks except management
|
||||
// which is not a navLink that ever gets disabled.
|
||||
case 'legacy_all':
|
||||
case 'no_kibana_privileges':
|
||||
expect(uiCapabilities.success).to.be(false);
|
||||
expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound);
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.only('management'));
|
||||
break;
|
||||
default:
|
||||
throw new UnreachableError(scenario);
|
||||
|
|
|
@ -8,11 +8,9 @@ import expect from '@kbn/expect';
|
|||
import { mapValues } from 'lodash';
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import { SavedObjectsManagementBuilder } from '../../common/saved_objects_management_builder';
|
||||
import {
|
||||
GetUICapabilitiesFailureReason,
|
||||
UICapabilitiesService,
|
||||
} from '../../common/services/ui_capabilities';
|
||||
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
|
||||
import { UserScenarios } from '../scenarios';
|
||||
import { assertDeeplyFalse } from '../../common/lib/assert_deeply_false';
|
||||
|
||||
const savedObjectsManagementBuilder = new SavedObjectsManagementBuilder(false);
|
||||
|
||||
|
@ -26,13 +24,15 @@ export default function savedObjectsManagementTests({
|
|||
UserScenarios.forEach(scenario => {
|
||||
it(`${scenario.fullName}`, async () => {
|
||||
const uiCapabilities = await uiCapabilitiesService.get({
|
||||
username: scenario.username,
|
||||
password: scenario.password,
|
||||
credentials: {
|
||||
username: scenario.username,
|
||||
password: scenario.password,
|
||||
},
|
||||
});
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
switch (scenario.username) {
|
||||
case 'superuser':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
const expected = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
|
||||
savedObjectsManagementBuilder.uiCapabilities('all')
|
||||
);
|
||||
|
@ -40,8 +40,6 @@ export default function savedObjectsManagementTests({
|
|||
break;
|
||||
case 'all':
|
||||
case 'dual_privileges_all':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
|
||||
savedObjectsManagementBuilder.build({
|
||||
all: [
|
||||
|
@ -64,8 +62,6 @@ export default function savedObjectsManagementTests({
|
|||
break;
|
||||
case 'read':
|
||||
case 'dual_privileges_read':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
|
||||
savedObjectsManagementBuilder.build({
|
||||
read: [
|
||||
|
@ -86,8 +82,6 @@ export default function savedObjectsManagementTests({
|
|||
);
|
||||
break;
|
||||
case 'foo_all':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
|
||||
savedObjectsManagementBuilder.build({
|
||||
all: ['foo', 'telemetry'],
|
||||
|
@ -96,8 +90,6 @@ export default function savedObjectsManagementTests({
|
|||
);
|
||||
break;
|
||||
case 'foo_read':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
|
||||
savedObjectsManagementBuilder.build({
|
||||
read: ['foo', 'index-pattern', 'config'],
|
||||
|
@ -105,9 +97,10 @@ export default function savedObjectsManagementTests({
|
|||
);
|
||||
break;
|
||||
case 'no_kibana_privileges':
|
||||
// these users have no access to any ui capabilities
|
||||
case 'legacy_all':
|
||||
expect(uiCapabilities.success).to.be(false);
|
||||
expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound);
|
||||
case 'no_kibana_privileges':
|
||||
assertDeeplyFalse(uiCapabilities.value!.savedObjectsManagement);
|
||||
break;
|
||||
default:
|
||||
throw new UnreachableError(scenario);
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau
|
|||
describe('catalogue', () => {
|
||||
SpaceScenarios.forEach(scenario => {
|
||||
it(`${scenario.name}`, async () => {
|
||||
const uiCapabilities = await uiCapabilitiesService.get(null, scenario.id);
|
||||
const uiCapabilities = await uiCapabilitiesService.get({ spaceId: scenario.id });
|
||||
switch (scenario.id) {
|
||||
case 'everything_space': {
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
|
|
|
@ -16,7 +16,7 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv
|
|||
describe('foo', () => {
|
||||
SpaceScenarios.forEach(scenario => {
|
||||
it(`${scenario.name}`, async () => {
|
||||
const uiCapabilities = await uiCapabilitiesService.get(null, scenario.id);
|
||||
const uiCapabilities = await uiCapabilitiesService.get({ spaceId: scenario.id });
|
||||
switch (scenario.id) {
|
||||
case 'everything_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
|
|
|
@ -25,7 +25,10 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul
|
|||
|
||||
SpaceScenarios.forEach(scenario => {
|
||||
it(`${scenario.name}`, async () => {
|
||||
const uiCapabilities = await uiCapabilitiesService.get(null, scenario.id);
|
||||
const uiCapabilities = await uiCapabilitiesService.get({
|
||||
navLinks: navLinksBuilder.all(),
|
||||
spaceId: scenario.id,
|
||||
});
|
||||
switch (scenario.id) {
|
||||
case 'everything_space':
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
|
|
|
@ -23,7 +23,7 @@ export default function savedObjectsManagementTests({
|
|||
SpaceScenarios.forEach(scenario => {
|
||||
it(`${scenario.name}`, async () => {
|
||||
// spaces don't affect saved objects management, so we assert the same thing for every scenario
|
||||
const uiCapabilities = await uiCapabilitiesService.get(null, scenario.id);
|
||||
const uiCapabilities = await uiCapabilitiesService.get({ spaceId: scenario.id });
|
||||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
|
||||
const expected = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
|
||||
|
|
2
x-pack/typings/hapi.d.ts
vendored
2
x-pack/typings/hapi.d.ts
vendored
|
@ -8,8 +8,8 @@ import 'hapi';
|
|||
|
||||
import { CloudPlugin } from '../plugins/cloud';
|
||||
import { EncryptedSavedObjectsPlugin } from '../plugins/encrypted_saved_objects';
|
||||
import { SecurityPlugin } from '../plugins/security';
|
||||
import { XPackMainPlugin } from '../plugins/xpack_main/xpack_main';
|
||||
import { SecurityPlugin } from '../plugins/security';
|
||||
|
||||
declare module 'hapi' {
|
||||
interface PluginProperties {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue