* provide ui settins client via context * update mocks * update types and expose setDefaults to plugins * move ui settings routes to NP * add typings fro test kbn server * move integration test & improve typings * hide client private methods, update tests * add unit tests for get_upgradable_config * inline writeErrors into createOrUpgradeConfig to simplify testing * regen docs * add functional tests for ui_settings service * unify test suites * add types for sipertest in core_plugin tests * tsify core_plugins tests * add test for empty saved config * update renovate * rename get/setDefaults to reguster * regen docs * regen docs * Update src/core/MIGRATION.md Co-Authored-By: Josh Dover <me@joshdover.com>
This commit is contained in:
parent
07caa65860
commit
c3f6af3ae3
|
@ -4,7 +4,7 @@
|
|||
|
||||
## IAnonymousPaths.isAnonymous() method
|
||||
|
||||
Determines whether the provided path doesn't require authentication
|
||||
Determines whether the provided path doesn't require authentication. `path` should include the current basePath.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -16,6 +16,6 @@ export interface IAnonymousPaths
|
|||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [isAnonymous(path)](./kibana-plugin-public.ianonymouspaths.isanonymous.md) | Determines whether the provided path doesn't require authentication |
|
||||
| [register(path)](./kibana-plugin-public.ianonymouspaths.register.md) | Register <code>path</code> as not requiring authentication |
|
||||
| [isAnonymous(path)](./kibana-plugin-public.ianonymouspaths.isanonymous.md) | Determines whether the provided path doesn't require authentication. <code>path</code> should include the current basePath. |
|
||||
| [register(path)](./kibana-plugin-public.ianonymouspaths.register.md) | Register <code>path</code> as not requiring authentication. <code>path</code> should not include the current basePath. |
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## IAnonymousPaths.register() method
|
||||
|
||||
Register `path` as not requiring authentication
|
||||
Register `path` as not requiring authentication. `path` should not include the current basePath.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -121,5 +121,5 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md)<!-- -->. |
|
||||
| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) |
|
||||
| [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) |
|
||||
| [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.md) | [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) |
|
||||
| [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) |
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ Gets the metadata about all uiSettings, including the type, default value, and u
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getAll(): UiSettingsState;
|
||||
getAll(): Record<string, UiSettingsParams & UserProvidedValues<any>>;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`UiSettingsState`
|
||||
`Record<string, UiSettingsParams & UserProvidedValues<any>>`
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## UiSettingsClientContract type
|
||||
|
||||
[UiSettingsClient](./kibana-plugin-public.uisettingsclient.md)
|
||||
Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -19,4 +19,5 @@ export interface CoreSetup
|
|||
| [context](./kibana-plugin-server.coresetup.context.md) | <code>ContextSetup</code> | [ContextSetup](./kibana-plugin-server.contextsetup.md) |
|
||||
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>ElasticsearchServiceSetup</code> | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) |
|
||||
| [http](./kibana-plugin-server.coresetup.http.md) | <code>HttpServiceSetup</code> | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) |
|
||||
| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | <code>UiSettingsServiceSetup</code> | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) |
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [uiSettings](./kibana-plugin-server.coresetup.uisettings.md)
|
||||
|
||||
## CoreSetup.uiSettings property
|
||||
|
||||
[UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
uiSettings: UiSettingsServiceSetup;
|
||||
```
|
|
@ -1,13 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getDefaults](./kibana-plugin-server.iuisettingsclient.getdefaults.md)
|
||||
|
||||
## IUiSettingsClient.getDefaults property
|
||||
|
||||
Returns uiSettings default values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getDefaults: () => Record<string, UiSettingsParams>;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getRegistered](./kibana-plugin-server.iuisettingsclient.getregistered.md)
|
||||
|
||||
## IUiSettingsClient.getRegistered property
|
||||
|
||||
Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getRegistered: () => Readonly<Record<string, UiSettingsParams>>;
|
||||
```
|
|
@ -9,8 +9,5 @@ Retrieves a set of all uiSettings values set by the user.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getUserProvided: <T extends SavedObjectAttribute = any>() => Promise<Record<string, {
|
||||
userValue?: T;
|
||||
isOverridden?: boolean;
|
||||
}>>;
|
||||
getUserProvided: <T extends SavedObjectAttribute = any>() => Promise<Record<string, UserProvidedValues<T>>>;
|
||||
```
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## IUiSettingsClient interface
|
||||
|
||||
Service that provides access to the UiSettings stored in elasticsearch.
|
||||
Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
@ -18,8 +18,8 @@ export interface IUiSettingsClient
|
|||
| --- | --- | --- |
|
||||
| [get](./kibana-plugin-server.iuisettingsclient.get.md) | <code><T extends SavedObjectAttribute = any>(key: string) => Promise<T></code> | Retrieves uiSettings values set by the user with fallbacks to default values if not specified. |
|
||||
| [getAll](./kibana-plugin-server.iuisettingsclient.getall.md) | <code><T extends SavedObjectAttribute = any>() => Promise<Record<string, T>></code> | Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. |
|
||||
| [getDefaults](./kibana-plugin-server.iuisettingsclient.getdefaults.md) | <code>() => Record<string, UiSettingsParams></code> | Returns uiSettings default values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) |
|
||||
| [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) | <code><T extends SavedObjectAttribute = any>() => Promise<Record<string, {</code><br/><code> userValue?: T;</code><br/><code> isOverridden?: boolean;</code><br/><code> }>></code> | Retrieves a set of all uiSettings values set by the user. |
|
||||
| [getRegistered](./kibana-plugin-server.iuisettingsclient.getregistered.md) | <code>() => Readonly<Record<string, UiSettingsParams>></code> | Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) |
|
||||
| [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) | <code><T extends SavedObjectAttribute = any>() => Promise<Record<string, UserProvidedValues<T>>></code> | Retrieves a set of all uiSettings values set by the user. |
|
||||
| [isOverridden](./kibana-plugin-server.iuisettingsclient.isoverridden.md) | <code>(key: string) => boolean</code> | Shows whether the uiSettings value set by the user. |
|
||||
| [remove](./kibana-plugin-server.iuisettingsclient.remove.md) | <code>(key: string) => Promise<void></code> | Removes uiSettings value by key. |
|
||||
| [removeMany](./kibana-plugin-server.iuisettingsclient.removemany.md) | <code>(keys: string[]) => Promise<void></code> | Removes multiple uiSettings values by keys. |
|
||||
|
|
|
@ -63,7 +63,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. |
|
||||
| [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | |
|
||||
| [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. |
|
||||
| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Service that provides access to the UiSettings stored in elasticsearch. |
|
||||
| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. |
|
||||
| [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. |
|
||||
| [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | |
|
||||
| [LegacyServiceSetupDeps](./kibana-plugin-server.legacyservicesetupdeps.md) | |
|
||||
|
@ -118,6 +118,8 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. |
|
||||
| [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request |
|
||||
| [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. |
|
||||
| [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | |
|
||||
| [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) | Describes the values explicitly set by user. |
|
||||
|
||||
## Variables
|
||||
|
||||
|
|
|
@ -15,5 +15,8 @@ core: {
|
|||
dataClient: IScopedClusterClient;
|
||||
adminClient: IScopedClusterClient;
|
||||
};
|
||||
uiSettings: {
|
||||
client: IUiSettingsClient;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
|
|
@ -18,5 +18,5 @@ export interface RequestHandlerContext
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [core](./kibana-plugin-server.requesthandlercontext.core.md) | <code>{</code><br/><code> savedObjects: {</code><br/><code> client: SavedObjectsClientContract;</code><br/><code> };</code><br/><code> elasticsearch: {</code><br/><code> dataClient: IScopedClusterClient;</code><br/><code> adminClient: IScopedClusterClient;</code><br/><code> };</code><br/><code> }</code> | |
|
||||
| [core](./kibana-plugin-server.requesthandlercontext.core.md) | <code>{</code><br/><code> savedObjects: {</code><br/><code> client: SavedObjectsClientContract;</code><br/><code> };</code><br/><code> elasticsearch: {</code><br/><code> dataClient: IScopedClusterClient;</code><br/><code> adminClient: IScopedClusterClient;</code><br/><code> };</code><br/><code> uiSettings: {</code><br/><code> client: IUiSettingsClient;</code><br/><code> };</code><br/><code> }</code> | |
|
||||
|
||||
|
|
|
@ -9,5 +9,5 @@ used to group the configured setting in the UI
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
category: string[];
|
||||
category?: string[];
|
||||
```
|
||||
|
|
|
@ -9,5 +9,5 @@ description provided to a user in UI
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
description: string;
|
||||
description?: string;
|
||||
```
|
||||
|
|
|
@ -20,7 +20,7 @@ export interface UiSettingsParams
|
|||
| [description](./kibana-plugin-server.uisettingsparams.description.md) | <code>string</code> | description provided to a user in UI |
|
||||
| [name](./kibana-plugin-server.uisettingsparams.name.md) | <code>string</code> | title in the UI |
|
||||
| [optionLabels](./kibana-plugin-server.uisettingsparams.optionlabels.md) | <code>Record<string, string></code> | text labels for 'select' type UI element |
|
||||
| [options](./kibana-plugin-server.uisettingsparams.options.md) | <code>string[]</code> | a range of valid values |
|
||||
| [options](./kibana-plugin-server.uisettingsparams.options.md) | <code>string[]</code> | array of permitted values for this setting |
|
||||
| [readonly](./kibana-plugin-server.uisettingsparams.readonly.md) | <code>boolean</code> | a flag indicating that value cannot be changed |
|
||||
| [requiresPageReload](./kibana-plugin-server.uisettingsparams.requirespagereload.md) | <code>boolean</code> | a flag indicating whether new value applying requires page reloading |
|
||||
| [type](./kibana-plugin-server.uisettingsparams.type.md) | <code>UiSettingsType</code> | defines a type of UI element [UiSettingsType](./kibana-plugin-server.uisettingstype.md) |
|
||||
|
|
|
@ -9,5 +9,5 @@ title in the UI
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
name: string;
|
||||
name?: string;
|
||||
```
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## UiSettingsParams.options property
|
||||
|
||||
a range of valid values
|
||||
array of permitted values for this setting
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -9,5 +9,5 @@ default value to fall back to if a user doesn't provide any
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
value: SavedObjectAttribute;
|
||||
value?: SavedObjectAttribute;
|
||||
```
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md)
|
||||
|
||||
## UiSettingsServiceSetup interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface UiSettingsServiceSetup
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [register(settings)](./kibana-plugin-server.uisettingsservicesetup.register.md) | Sets settings with default values for the uiSettings. |
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) > [register](./kibana-plugin-server.uisettingsservicesetup.register.md)
|
||||
|
||||
## UiSettingsServiceSetup.register() method
|
||||
|
||||
Sets settings with default values for the uiSettings.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
register(settings: Record<string, UiSettingsParams>): void;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| settings | <code>Record<string, UiSettingsParams></code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`void`
|
||||
|
||||
## Example
|
||||
|
||||
setup(core: CoreSetup)<!-- -->{ core.uiSettings.register(\[{ foo: { name: i18n.translate('my foo settings'), value: true, description: 'add some awesomeness', }<!-- -->, }<!-- -->\]); }
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) > [isOverridden](./kibana-plugin-server.userprovidedvalues.isoverridden.md)
|
||||
|
||||
## UserProvidedValues.isOverridden property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
isOverridden?: boolean;
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md)
|
||||
|
||||
## UserProvidedValues interface
|
||||
|
||||
Describes the values explicitly set by user.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface UserProvidedValues<T extends SavedObjectAttribute = any>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [isOverridden](./kibana-plugin-server.userprovidedvalues.isoverridden.md) | <code>boolean</code> | |
|
||||
| [userValue](./kibana-plugin-server.userprovidedvalues.uservalue.md) | <code>T</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) > [userValue](./kibana-plugin-server.userprovidedvalues.uservalue.md)
|
||||
|
||||
## UserProvidedValues.userValue property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
userValue?: T;
|
||||
```
|
|
@ -337,6 +337,7 @@
|
|||
"@types/strip-ansi": "^3.0.0",
|
||||
"@types/styled-components": "^3.0.2",
|
||||
"@types/supertest": "^2.0.5",
|
||||
"@types/supertest-as-promised": "^2.0.38",
|
||||
"@types/type-detect": "^4.0.1",
|
||||
"@types/uuid": "^3.4.4",
|
||||
"@types/vinyl-fs": "^2.4.11",
|
||||
|
|
|
@ -567,6 +567,14 @@
|
|||
'@types/supertest',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupSlug: 'supertest-as-promised',
|
||||
groupName: 'supertest-as-promised related packages',
|
||||
packageNames: [
|
||||
'supertest-as-promised',
|
||||
'@types/supertest-as-promised',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupSlug: 'type-detect',
|
||||
groupName: 'type-detect related packages',
|
||||
|
|
|
@ -19,8 +19,12 @@
|
|||
|
||||
import { get } from 'lodash';
|
||||
import { DiscoveredPlugin, PluginName } from '../../server';
|
||||
import { EnvironmentMode, PackageInfo } from '../../server/types';
|
||||
import { UiSettingsState } from '../ui_settings';
|
||||
import {
|
||||
EnvironmentMode,
|
||||
PackageInfo,
|
||||
UiSettingsParams,
|
||||
UserProvidedValues,
|
||||
} from '../../server/types';
|
||||
import { deepFreeze } from '../../utils/';
|
||||
import { Capabilities } from '..';
|
||||
|
||||
|
@ -69,8 +73,8 @@ export interface InjectedMetadataParams {
|
|||
serverName: string;
|
||||
devMode: boolean;
|
||||
uiSettings: {
|
||||
defaults: UiSettingsState;
|
||||
user?: UiSettingsState;
|
||||
defaults: Record<string, UiSettingsParams>;
|
||||
user?: Record<string, UserProvidedValues>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -179,8 +183,8 @@ export interface InjectedMetadataSetup {
|
|||
serverName: string;
|
||||
devMode: boolean;
|
||||
uiSettings: {
|
||||
defaults: UiSettingsState;
|
||||
user?: UiSettingsState | undefined;
|
||||
defaults: Record<string, UiSettingsParams>;
|
||||
user?: Record<string, UserProvidedValues> | undefined;
|
||||
};
|
||||
};
|
||||
getInjectedVar: (name: string, defaultValue?: any) => unknown;
|
||||
|
|
|
@ -11,6 +11,8 @@ import React from 'react';
|
|||
import * as Rx from 'rxjs';
|
||||
import { ShallowPromise } from '@kbn/utility-types';
|
||||
import { EuiGlobalToastListToast as Toast } from '@elastic/eui';
|
||||
import { UiSettingsParams as UiSettingsParams_2 } from 'src/core/server/types';
|
||||
import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types';
|
||||
|
||||
// @public
|
||||
export interface App extends AppBase {
|
||||
|
@ -957,7 +959,7 @@ export class UiSettingsClient {
|
|||
constructor(params: UiSettingsClientParams);
|
||||
get$(key: string, defaultOverride?: any): Rx.Observable<any>;
|
||||
get(key: string, defaultOverride?: any): any;
|
||||
getAll(): UiSettingsState;
|
||||
getAll(): Record<string, UiSettingsParams_2 & UserProvidedValues_2<any>>;
|
||||
getSaved$(): Rx.Observable<{
|
||||
key: string;
|
||||
newValue: any;
|
||||
|
@ -979,16 +981,13 @@ export class UiSettingsClient {
|
|||
stop(): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type UiSettingsClientContract = PublicMethodsOf<UiSettingsClient>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UiSettingsState {
|
||||
// Warning: (ae-forgotten-export) The symbol "InjectedUiSettingsDefault" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "InjectedUiSettingsUser" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
[key: string]: InjectedUiSettingsDefault & InjectedUiSettingsUser;
|
||||
[key: string]: UiSettingsParams_2 & UserProvidedValues_2;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,28 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// properties that come from legacyInjectedMetadata.uiSettings.defaults
|
||||
interface InjectedUiSettingsDefault {
|
||||
name?: string;
|
||||
value?: any;
|
||||
description?: string;
|
||||
category?: string[];
|
||||
type?: string;
|
||||
readOnly?: boolean;
|
||||
options?: string[] | { [key: string]: any };
|
||||
/**
|
||||
* Whether a change in that setting will only take affect after a page reload.
|
||||
*/
|
||||
requiresPageReload?: boolean;
|
||||
}
|
||||
|
||||
// properties that come from legacyInjectedMetadata.uiSettings.user
|
||||
interface InjectedUiSettingsUser {
|
||||
userValue?: any;
|
||||
isOverridden?: boolean;
|
||||
}
|
||||
import { UiSettingsParams, UserProvidedValues } from 'src/core/server/types';
|
||||
|
||||
/** @public */
|
||||
export interface UiSettingsState {
|
||||
[key: string]: InjectedUiSettingsDefault & InjectedUiSettingsUser;
|
||||
[key: string]: UiSettingsParams & UserProvidedValues;
|
||||
}
|
||||
|
|
|
@ -21,18 +21,25 @@ import { cloneDeep, defaultsDeep } from 'lodash';
|
|||
import * as Rx from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
import { UiSettingsParams, UserProvidedValues } from 'src/core/server/types';
|
||||
import { UiSettingsState } from './types';
|
||||
|
||||
import { UiSettingsApi } from './ui_settings_api';
|
||||
|
||||
/** @public */
|
||||
interface UiSettingsClientParams {
|
||||
api: UiSettingsApi;
|
||||
defaults: UiSettingsState;
|
||||
defaults: Record<string, UiSettingsParams>;
|
||||
initialSettings?: UiSettingsState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Client-side client that provides access to the advanced settings stored in elasticsearch.
|
||||
* The settings provide control over the behavior of the Kibana application.
|
||||
* For example, a user can specify how to display numeric or date fields.
|
||||
* Users can adjust the settings via Management UI.
|
||||
* {@link UiSettingsClient}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type UiSettingsClientContract = PublicMethodsOf<UiSettingsClient>;
|
||||
|
@ -44,8 +51,8 @@ export class UiSettingsClient {
|
|||
private readonly updateErrors$ = new Rx.Subject<Error>();
|
||||
|
||||
private readonly api: UiSettingsApi;
|
||||
private readonly defaults: UiSettingsState;
|
||||
private cache: UiSettingsState;
|
||||
private readonly defaults: Record<string, UiSettingsParams>;
|
||||
private cache: Record<string, UiSettingsParams & UserProvidedValues>;
|
||||
|
||||
constructor(params: UiSettingsClientParams) {
|
||||
this.api = params.api;
|
||||
|
|
|
@ -120,7 +120,11 @@ export class HapiResponseAdapter {
|
|||
});
|
||||
|
||||
error.output.payload.message = getErrorMessage(payload);
|
||||
error.output.payload.attributes = getErrorAttributes(payload);
|
||||
|
||||
const attributes = getErrorAttributes(payload);
|
||||
if (attributes) {
|
||||
error.output.payload.attributes = attributes;
|
||||
}
|
||||
|
||||
const headers = kibanaResponse.options.headers;
|
||||
if (headers) {
|
||||
|
|
|
@ -49,7 +49,11 @@ import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plug
|
|||
import { ContextSetup } from './context';
|
||||
import { SavedObjectsServiceStart } from './saved_objects';
|
||||
|
||||
import { InternalUiSettingsServiceSetup } from './ui_settings';
|
||||
import {
|
||||
InternalUiSettingsServiceSetup,
|
||||
IUiSettingsClient,
|
||||
UiSettingsServiceSetup,
|
||||
} from './ui_settings';
|
||||
import { SavedObjectsClientContract } from './saved_objects/types';
|
||||
|
||||
export { bootstrap } from './bootstrap';
|
||||
|
@ -175,6 +179,8 @@ export {
|
|||
UiSettingsParams,
|
||||
InternalUiSettingsServiceSetup,
|
||||
UiSettingsType,
|
||||
UiSettingsServiceSetup,
|
||||
UserProvidedValues,
|
||||
} from './ui_settings';
|
||||
|
||||
export { RecursiveReadonly } from '../utils';
|
||||
|
@ -216,6 +222,9 @@ export interface RequestHandlerContext {
|
|||
dataClient: IScopedClusterClient;
|
||||
adminClient: IScopedClusterClient;
|
||||
};
|
||||
uiSettings: {
|
||||
client: IUiSettingsClient;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -231,6 +240,8 @@ export interface CoreSetup {
|
|||
elasticsearch: ElasticsearchServiceSetup;
|
||||
/** {@link HttpServiceSetup} */
|
||||
http: HttpServiceSetup;
|
||||
/** {@link UiSettingsServiceSetup} */
|
||||
uiSettings: UiSettingsServiceSetup;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -72,7 +72,7 @@ beforeEach(() => {
|
|||
core: {
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
elasticsearch: { legacy: {} } as any,
|
||||
uiSettings: uiSettingsServiceMock.createSetup(),
|
||||
uiSettings: uiSettingsServiceMock.createSetupContract(),
|
||||
http: {
|
||||
...httpServiceMock.createSetupContract(),
|
||||
auth: {
|
||||
|
|
|
@ -248,6 +248,9 @@ export class LegacyService implements CoreService<LegacyServiceSetup> {
|
|||
basePath: setupDeps.core.http.basePath,
|
||||
isTlsEnabled: setupDeps.core.http.isTlsEnabled,
|
||||
},
|
||||
uiSettings: {
|
||||
register: setupDeps.core.uiSettings.register,
|
||||
},
|
||||
};
|
||||
const coreStart: CoreStart = {};
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import { loggingServiceMock } from './logging/logging_service.mock';
|
|||
import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock';
|
||||
import { httpServiceMock } from './http/http_service.mock';
|
||||
import { contextServiceMock } from './context/context_service.mock';
|
||||
import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
||||
|
||||
export { httpServerMock } from './http/http_server.mocks';
|
||||
export { sessionStorageMock } from './http/cookie_session_storage.mocks';
|
||||
|
@ -79,10 +80,14 @@ function createCoreSetupMock() {
|
|||
};
|
||||
httpMock.createRouter.mockImplementation(() => httpService.createRouter(''));
|
||||
|
||||
const uiSettingsMock = {
|
||||
register: uiSettingsServiceMock.createSetupContract().register,
|
||||
};
|
||||
const mock: MockedKeys<CoreSetup> = {
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
elasticsearch: elasticsearchServiceMock.createSetupContract(),
|
||||
http: httpMock,
|
||||
uiSettings: uiSettingsMock,
|
||||
};
|
||||
|
||||
return mock;
|
||||
|
@ -94,8 +99,19 @@ function createCoreStartMock() {
|
|||
return mock;
|
||||
}
|
||||
|
||||
function createInternalCoreSetupMock() {
|
||||
const setupDeps = {
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
elasticsearch: elasticsearchServiceMock.createSetupContract(),
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
uiSettings: uiSettingsServiceMock.createSetupContract(),
|
||||
};
|
||||
return setupDeps;
|
||||
}
|
||||
|
||||
export const coreMock = {
|
||||
createSetup: createCoreSetupMock,
|
||||
createStart: createCoreStartMock,
|
||||
createInternalSetup: createInternalCoreSetupMock,
|
||||
createPluginInitializerContext: pluginInitializerContextMock,
|
||||
};
|
||||
|
|
|
@ -24,15 +24,13 @@ import { schema } from '@kbn/config-schema';
|
|||
import { Env } from '../config';
|
||||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { coreMock } from '../mocks';
|
||||
import { configServiceMock } from '../config/config_service.mock';
|
||||
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
|
||||
import { PluginWrapper } from './plugin';
|
||||
import { PluginManifest } from './types';
|
||||
import { createPluginInitializerContext, createPluginSetupContext } from './plugin_context';
|
||||
import { contextServiceMock } from '../context/context_service.mock';
|
||||
|
||||
const mockPluginInitializer = jest.fn();
|
||||
const logger = loggingServiceMock.create();
|
||||
|
@ -68,11 +66,7 @@ configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true }));
|
|||
let coreId: symbol;
|
||||
let env: Env;
|
||||
let coreContext: CoreContext;
|
||||
const setupDeps = {
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
elasticsearch: elasticsearchServiceMock.createSetupContract(),
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
};
|
||||
const setupDeps = coreMock.createInternalSetup();
|
||||
beforeEach(() => {
|
||||
coreId = Symbol('core');
|
||||
env = Env.createDefault(getEnvOptions());
|
||||
|
|
|
@ -123,6 +123,9 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
|
|||
basePath: deps.http.basePath,
|
||||
isTlsEnabled: deps.http.isTlsEnabled,
|
||||
},
|
||||
uiSettings: {
|
||||
register: deps.uiSettings.register,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -25,15 +25,13 @@ import { schema } from '@kbn/config-schema';
|
|||
|
||||
import { Config, ConfigPath, ConfigService, Env, ObjectToConfigAdapter } from '../config';
|
||||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { coreMock } from '../mocks';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
import { PluginDiscoveryError } from './discovery';
|
||||
import { PluginWrapper } from './plugin';
|
||||
import { PluginsService } from './plugins_service';
|
||||
import { PluginsSystem } from './plugins_system';
|
||||
import { config } from './plugins_config';
|
||||
import { contextServiceMock } from '../context/context_service.mock';
|
||||
|
||||
const MockPluginsSystem: jest.Mock<PluginsSystem> = PluginsSystem as any;
|
||||
|
||||
|
@ -42,11 +40,7 @@ let configService: ConfigService;
|
|||
let coreId: symbol;
|
||||
let env: Env;
|
||||
let mockPluginSystem: jest.Mocked<PluginsSystem>;
|
||||
const setupDeps = {
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
elasticsearch: elasticsearchServiceMock.createSetupContract(),
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
};
|
||||
const setupDeps = coreMock.createInternalSetup();
|
||||
const logger = loggingServiceMock.create();
|
||||
|
||||
['path-1', 'path-2', 'path-3', 'path-4', 'path-5'].forEach(path => {
|
||||
|
|
|
@ -21,15 +21,14 @@ import { Observable } from 'rxjs';
|
|||
import { filter, first, map, mergeMap, tap, toArray } from 'rxjs/operators';
|
||||
import { CoreService } from '../../types';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { InternalElasticsearchServiceSetup } from '../elasticsearch';
|
||||
import { InternalHttpServiceSetup } from '../http';
|
||||
|
||||
import { Logger } from '../logging';
|
||||
import { discover, PluginDiscoveryError, PluginDiscoveryErrorType } from './discovery';
|
||||
import { PluginWrapper } from './plugin';
|
||||
import { DiscoveredPlugin, DiscoveredPluginInternal, PluginName } from './types';
|
||||
import { PluginsConfig, PluginsConfigType } from './plugins_config';
|
||||
import { PluginsSystem } from './plugins_system';
|
||||
import { ContextSetup } from '../context';
|
||||
import { InternalCoreSetup } from '..';
|
||||
|
||||
/** @public */
|
||||
export interface PluginsServiceSetup {
|
||||
|
@ -46,11 +45,7 @@ export interface PluginsServiceStart {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
export interface PluginsServiceSetupDeps {
|
||||
context: ContextSetup;
|
||||
elasticsearch: InternalElasticsearchServiceSetup;
|
||||
http: InternalHttpServiceSetup;
|
||||
}
|
||||
export type PluginsServiceSetupDeps = InternalCoreSetup;
|
||||
|
||||
/** @internal */
|
||||
export interface PluginsServiceStartDeps {} // eslint-disable-line @typescript-eslint/no-empty-interface
|
||||
|
|
|
@ -28,13 +28,13 @@ import { Env } from '../config';
|
|||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { configServiceMock } from '../config/config_service.mock';
|
||||
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
|
||||
import { PluginWrapper } from './plugin';
|
||||
import { PluginName } from './types';
|
||||
import { PluginsSystem } from './plugins_system';
|
||||
import { contextServiceMock } from '../context/context_service.mock';
|
||||
|
||||
import { coreMock } from '../mocks';
|
||||
|
||||
const logger = loggingServiceMock.create();
|
||||
function createPlugin(
|
||||
|
@ -68,11 +68,8 @@ const configService = configServiceMock.create();
|
|||
configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true }));
|
||||
let env: Env;
|
||||
let coreContext: CoreContext;
|
||||
const setupDeps = {
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
elasticsearch: elasticsearchServiceMock.createSetupContract(),
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
};
|
||||
const setupDeps = coreMock.createInternalSetup();
|
||||
|
||||
beforeEach(() => {
|
||||
env = Env.createDefault(getEnvOptions());
|
||||
|
||||
|
|
|
@ -511,6 +511,8 @@ export interface CoreSetup {
|
|||
elasticsearch: ElasticsearchServiceSetup;
|
||||
// (undocumented)
|
||||
http: HttpServiceSetup;
|
||||
// (undocumented)
|
||||
uiSettings: UiSettingsServiceSetup;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
@ -737,7 +739,7 @@ export interface InternalCoreStart {
|
|||
// @internal (undocumented)
|
||||
export interface InternalUiSettingsServiceSetup {
|
||||
asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient;
|
||||
setDefaults(values: Record<string, UiSettingsParams>): void;
|
||||
register(settings: Record<string, UiSettingsParams>): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
@ -763,11 +765,8 @@ export type IScopedClusterClient = Pick<ScopedClusterClient, 'callAsCurrentUser'
|
|||
export interface IUiSettingsClient {
|
||||
get: <T extends SavedObjectAttribute = any>(key: string) => Promise<T>;
|
||||
getAll: <T extends SavedObjectAttribute = any>() => Promise<Record<string, T>>;
|
||||
getDefaults: () => Record<string, UiSettingsParams>;
|
||||
getUserProvided: <T extends SavedObjectAttribute = any>() => Promise<Record<string, {
|
||||
userValue?: T;
|
||||
isOverridden?: boolean;
|
||||
}>>;
|
||||
getRegistered: () => Readonly<Record<string, UiSettingsParams>>;
|
||||
getUserProvided: <T extends SavedObjectAttribute = any>() => Promise<Record<string, UserProvidedValues<T>>>;
|
||||
isOverridden: (key: string) => boolean;
|
||||
remove: (key: string) => Promise<void>;
|
||||
removeMany: (keys: string[]) => Promise<void>;
|
||||
|
@ -1074,6 +1073,9 @@ export interface RequestHandlerContext {
|
|||
dataClient: IScopedClusterClient;
|
||||
adminClient: IScopedClusterClient;
|
||||
};
|
||||
uiSettings: {
|
||||
client: IUiSettingsClient;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1610,24 +1612,37 @@ export interface SessionStorageFactory<T> {
|
|||
|
||||
// @public
|
||||
export interface UiSettingsParams {
|
||||
category: string[];
|
||||
description: string;
|
||||
name: string;
|
||||
category?: string[];
|
||||
description?: string;
|
||||
name?: string;
|
||||
optionLabels?: Record<string, string>;
|
||||
options?: string[];
|
||||
readonly?: boolean;
|
||||
requiresPageReload?: boolean;
|
||||
type?: UiSettingsType;
|
||||
value: SavedObjectAttribute;
|
||||
value?: SavedObjectAttribute;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UiSettingsServiceSetup {
|
||||
register(settings: Record<string, UiSettingsParams>): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string';
|
||||
|
||||
// @public
|
||||
export interface UserProvidedValues<T extends SavedObjectAttribute = any> {
|
||||
// (undocumented)
|
||||
isOverridden?: boolean;
|
||||
// (undocumented)
|
||||
userValue?: T;
|
||||
}
|
||||
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/plugins_service.ts:39:5 - (ae-forgotten-export) The symbol "DiscoveredPluginInternal" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/plugins_service.ts:38:5 - (ae-forgotten-export) The symbol "DiscoveredPluginInternal" needs to be exported by the entry point index.d.ts
|
||||
|
||||
```
|
||||
|
|
|
@ -21,7 +21,7 @@ import { take } from 'rxjs/operators';
|
|||
import { Type } from '@kbn/config-schema';
|
||||
|
||||
import { ConfigService, Env, Config, ConfigPath } from './config';
|
||||
import { ElasticsearchService, ElasticsearchServiceSetup } from './elasticsearch';
|
||||
import { ElasticsearchService } from './elasticsearch';
|
||||
import { HttpService, InternalHttpServiceSetup } from './http';
|
||||
import { LegacyService } from './legacy';
|
||||
import { Logger, LoggerFactory } from './logging';
|
||||
|
@ -39,7 +39,7 @@ import { config as uiSettingsConfig } from './ui_settings';
|
|||
import { mapToObject } from '../utils/';
|
||||
import { ContextService } from './context';
|
||||
import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service';
|
||||
import { RequestHandlerContext } from '.';
|
||||
import { RequestHandlerContext, InternalCoreSetup } from '.';
|
||||
|
||||
const coreId = Symbol('core');
|
||||
|
||||
|
@ -102,7 +102,7 @@ export class Server {
|
|||
http: httpSetup,
|
||||
});
|
||||
|
||||
const coreSetup = {
|
||||
const coreSetup: InternalCoreSetup = {
|
||||
context: contextServiceSetup,
|
||||
elasticsearch: elasticsearchServiceSetup,
|
||||
http: httpSetup,
|
||||
|
@ -121,7 +121,7 @@ export class Server {
|
|||
legacy: legacySetup,
|
||||
});
|
||||
|
||||
this.registerCoreContext({ ...coreSetup, savedObjects: savedObjectsSetup });
|
||||
this.registerCoreContext(coreSetup, savedObjectsSetup);
|
||||
|
||||
return coreSetup;
|
||||
}
|
||||
|
@ -163,27 +163,31 @@ export class Server {
|
|||
);
|
||||
}
|
||||
|
||||
private registerCoreContext(coreSetup: {
|
||||
http: InternalHttpServiceSetup;
|
||||
elasticsearch: ElasticsearchServiceSetup;
|
||||
savedObjects: SavedObjectsServiceSetup;
|
||||
}) {
|
||||
private registerCoreContext(
|
||||
coreSetup: InternalCoreSetup,
|
||||
savedObjects: SavedObjectsServiceSetup
|
||||
) {
|
||||
coreSetup.http.registerRouteHandlerContext(
|
||||
coreId,
|
||||
'core',
|
||||
async (context, req): Promise<RequestHandlerContext['core']> => {
|
||||
const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise();
|
||||
const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise();
|
||||
const savedObjectsClient = savedObjects.clientProvider.getClient(req);
|
||||
|
||||
return {
|
||||
savedObjects: {
|
||||
// Note: the client provider doesn't support new ES clients
|
||||
// emitted from adminClient$
|
||||
client: coreSetup.savedObjects.clientProvider.getClient(req),
|
||||
client: savedObjectsClient,
|
||||
},
|
||||
elasticsearch: {
|
||||
adminClient: adminClient.asScoped(req),
|
||||
dataClient: dataClient.asScoped(req),
|
||||
},
|
||||
uiSettings: {
|
||||
client: coreSetup.uiSettings.asScopedToClient(savedObjectsClient),
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -20,4 +20,5 @@
|
|||
/** This module is intended for consumption by public to avoid import issues with server-side code */
|
||||
export { PluginOpaqueId } from './plugins/types';
|
||||
export * from './saved_objects/types';
|
||||
export * from './ui_settings/types';
|
||||
export { EnvironmentMode, PackageInfo } from './config/types';
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
import sinon from 'sinon';
|
||||
import Chance from 'chance';
|
||||
|
||||
import { SavedObjectsErrorHelpers } from '../../saved_objects';
|
||||
|
||||
import { loggingServiceMock } from '../../logging/logging_service.mock';
|
||||
import * as getUpgradeableConfigNS from './get_upgradeable_config';
|
||||
import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
|
||||
|
@ -50,6 +52,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() {
|
|||
version,
|
||||
buildNum,
|
||||
log: logger.get(),
|
||||
handleWriteErrors: false,
|
||||
...options,
|
||||
});
|
||||
|
||||
|
@ -173,85 +176,64 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('onWriteError()', () => {
|
||||
it('is called with error and attributes when savedObjectsClient.create rejects', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
|
||||
const error = new Error('foo');
|
||||
savedObjectsClient.create.callsFake(async () => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
const onWriteError = sinon.stub();
|
||||
await run({ onWriteError });
|
||||
sinon.assert.calledOnce(onWriteError);
|
||||
sinon.assert.calledWithExactly(onWriteError, error, {
|
||||
buildNum,
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves with the return value of onWriteError()', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
|
||||
savedObjectsClient.create.callsFake(async () => {
|
||||
throw new Error('foo');
|
||||
});
|
||||
|
||||
const result = await run({ onWriteError: () => 123 });
|
||||
expect(result).toBe(123);
|
||||
});
|
||||
|
||||
it('rejects with the error from onWriteError() if it rejects', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
|
||||
savedObjectsClient.create.callsFake(async () => {
|
||||
throw new Error('foo');
|
||||
});
|
||||
|
||||
try {
|
||||
await run({
|
||||
onWriteError: (error: Error) => Promise.reject(new Error(`${error.message} bar`)),
|
||||
describe('handleWriteErrors', () => {
|
||||
describe('handleWriteErrors: false', () => {
|
||||
it('throws write errors', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
const error = new Error('foo');
|
||||
savedObjectsClient.create.callsFake(async () => {
|
||||
throw error;
|
||||
});
|
||||
throw new Error('expected run() to reject');
|
||||
} catch (error) {
|
||||
expect(error.message).toBe('foo bar');
|
||||
}
|
||||
|
||||
await expect(run({ handleWriteErrors: false })).rejects.toThrowError(error);
|
||||
});
|
||||
});
|
||||
describe('handleWriteErrors:true', () => {
|
||||
it('returns undefined for ConflictError', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
const error = new Error('foo');
|
||||
savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateConflictError(error));
|
||||
|
||||
it('rejects with the error from onWriteError() if it throws sync', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
|
||||
savedObjectsClient.create.callsFake(async () => {
|
||||
throw new Error('foo');
|
||||
expect(await run({ handleWriteErrors: true })).toBe(undefined);
|
||||
});
|
||||
|
||||
try {
|
||||
await run({
|
||||
onWriteError: (error: Error) => {
|
||||
throw new Error(`${error.message} bar`);
|
||||
},
|
||||
it('returns config attributes for NotAuthorizedError', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
const error = new Error('foo');
|
||||
savedObjectsClient.create.throws(
|
||||
SavedObjectsErrorHelpers.decorateNotAuthorizedError(error)
|
||||
);
|
||||
|
||||
expect(await run({ handleWriteErrors: true })).toEqual({
|
||||
buildNum,
|
||||
});
|
||||
throw new Error('expected run() to reject');
|
||||
} catch (error) {
|
||||
expect(error.message).toBe('foo bar');
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects with the writeError if onWriteError() is undefined', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
|
||||
savedObjectsClient.create.callsFake(async () => {
|
||||
throw new Error('foo');
|
||||
});
|
||||
|
||||
try {
|
||||
await run({
|
||||
onWriteError: undefined,
|
||||
it('returns config attributes for ForbiddenError', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
const error = new Error('foo');
|
||||
savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateForbiddenError(error));
|
||||
|
||||
expect(await run({ handleWriteErrors: true })).toEqual({
|
||||
buildNum,
|
||||
});
|
||||
throw new Error('expected run() to reject');
|
||||
} catch (error) {
|
||||
expect(error.message).toBe('foo');
|
||||
}
|
||||
});
|
||||
|
||||
it('throws error for other SavedObjects exceptions', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
const error = new Error('foo');
|
||||
savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateGeneralError(error));
|
||||
|
||||
await expect(run({ handleWriteErrors: true })).rejects.toThrowError(error);
|
||||
});
|
||||
|
||||
it('throws error for all other exceptions', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
const error = new Error('foo');
|
||||
savedObjectsClient.create.throws(error);
|
||||
|
||||
await expect(run({ handleWriteErrors: true })).rejects.toThrowError(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import { defaults } from 'lodash';
|
||||
|
||||
import { SavedObjectsClientContract, SavedObjectAttribute } from '../../saved_objects/types';
|
||||
import { SavedObjectsErrorHelpers } from '../../saved_objects/';
|
||||
import { Logger } from '../../logging';
|
||||
|
||||
import { getUpgradeableConfig } from './get_upgradeable_config';
|
||||
|
@ -29,15 +30,13 @@ interface Options {
|
|||
version: string;
|
||||
buildNum: number;
|
||||
log: Logger;
|
||||
onWriteError?: <T extends SavedObjectAttribute = any>(
|
||||
error: Error,
|
||||
attributes: Record<string, any>
|
||||
) => Record<string, T> | undefined;
|
||||
handleWriteErrors: boolean;
|
||||
}
|
||||
|
||||
export async function createOrUpgradeSavedConfig<T extends SavedObjectAttribute = any>(
|
||||
options: Options
|
||||
): Promise<Record<string, T> | undefined> {
|
||||
const { savedObjectsClient, version, buildNum, log, onWriteError } = options;
|
||||
const { savedObjectsClient, version, buildNum, log, handleWriteErrors } = options;
|
||||
|
||||
// try to find an older config we can upgrade
|
||||
const upgradeableConfig = await getUpgradeableConfig({
|
||||
|
@ -52,8 +51,17 @@ export async function createOrUpgradeSavedConfig<T extends SavedObjectAttribute
|
|||
// create the new SavedConfig
|
||||
await savedObjectsClient.create('config', attributes, { id: version });
|
||||
} catch (error) {
|
||||
if (onWriteError) {
|
||||
return onWriteError(error, attributes);
|
||||
if (handleWriteErrors) {
|
||||
if (SavedObjectsErrorHelpers.isConflictError(error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
SavedObjectsErrorHelpers.isNotAuthorizedError(error) ||
|
||||
SavedObjectsErrorHelpers.isForbiddenError(error)
|
||||
) {
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 { getUpgradeableConfig } from './get_upgradeable_config';
|
||||
import { savedObjectsClientMock } from '../../saved_objects/service/saved_objects_client.mock';
|
||||
|
||||
describe('getUpgradeableConfig', () => {
|
||||
it('finds saved objects with type "config"', async () => {
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
saved_objects: [{ id: '7.5.0' }],
|
||||
} as any);
|
||||
|
||||
await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' });
|
||||
expect(savedObjectsClient.find.mock.calls[0][0].type).toBe('config');
|
||||
});
|
||||
|
||||
it('finds saved config with version < than Kibana version', async () => {
|
||||
const savedConfig = { id: '7.4.0' };
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
saved_objects: [savedConfig],
|
||||
} as any);
|
||||
|
||||
const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' });
|
||||
expect(result).toBe(savedConfig);
|
||||
});
|
||||
|
||||
it('finds saved config with RC version === Kibana version', async () => {
|
||||
const savedConfig = { id: '7.5.0-rc1' };
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
saved_objects: [savedConfig],
|
||||
} as any);
|
||||
|
||||
const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' });
|
||||
expect(result).toBe(savedConfig);
|
||||
});
|
||||
|
||||
it('does not find saved config with version === Kibana version', async () => {
|
||||
const savedConfig = { id: '7.5.0' };
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
saved_objects: [savedConfig],
|
||||
} as any);
|
||||
|
||||
const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' });
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
|
||||
it('does not find saved config with version > Kibana version', async () => {
|
||||
const savedConfig = { id: '7.6.0' };
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
saved_objects: [savedConfig],
|
||||
} as any);
|
||||
|
||||
const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' });
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
|
||||
it('handles empty config', async () => {
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
saved_objects: [],
|
||||
} as any);
|
||||
|
||||
const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' });
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
});
|
|
@ -18,28 +18,31 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { UnwrapPromise } from '@kbn/utility-types';
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
|
||||
import KbnServer from '../../../../../legacy/server/kbn_server';
|
||||
import { createTestServers } from '../../../../../test_utils/kbn_server';
|
||||
import {
|
||||
createTestServers,
|
||||
TestElasticsearchUtils,
|
||||
TestKibanaUtils,
|
||||
TestUtils,
|
||||
} from '../../../../../test_utils/kbn_server';
|
||||
import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config';
|
||||
import { loggingServiceMock } from '../../../logging/logging_service.mock';
|
||||
|
||||
const logger = loggingServiceMock.create().get();
|
||||
describe('createOrUpgradeSavedConfig()', () => {
|
||||
let savedObjectsClient: SavedObjectsClientContract;
|
||||
let kbnServer: KbnServer;
|
||||
let servers: ReturnType<typeof createTestServers>;
|
||||
let esServer: UnwrapPromise<ReturnType<typeof servers['startES']>>;
|
||||
let kbn: UnwrapPromise<ReturnType<typeof servers['startKibana']>>;
|
||||
let servers: TestUtils;
|
||||
let esServer: TestElasticsearchUtils;
|
||||
let kbn: TestKibanaUtils;
|
||||
|
||||
let kbnServer: TestKibanaUtils['kbnServer'];
|
||||
|
||||
beforeAll(async function() {
|
||||
servers = createTestServers({
|
||||
adjustTimeout: t => {
|
||||
jest.setTimeout(t);
|
||||
},
|
||||
settings: {},
|
||||
});
|
||||
esServer = await servers.startES();
|
||||
kbn = await servers.startKibana();
|
||||
|
@ -90,6 +93,7 @@ describe('createOrUpgradeSavedConfig()', () => {
|
|||
version: '5.4.0',
|
||||
buildNum: 54099,
|
||||
log: logger,
|
||||
handleWriteErrors: false,
|
||||
});
|
||||
|
||||
const config540 = await savedObjectsClient.get('config', '5.4.0');
|
||||
|
@ -116,6 +120,7 @@ describe('createOrUpgradeSavedConfig()', () => {
|
|||
version: '5.4.1',
|
||||
buildNum: 54199,
|
||||
log: logger,
|
||||
handleWriteErrors: false,
|
||||
});
|
||||
|
||||
const config541 = await savedObjectsClient.get('config', '5.4.1');
|
||||
|
@ -142,6 +147,7 @@ describe('createOrUpgradeSavedConfig()', () => {
|
|||
version: '7.0.0-rc1',
|
||||
buildNum: 70010,
|
||||
log: logger,
|
||||
handleWriteErrors: false,
|
||||
});
|
||||
|
||||
const config700rc1 = await savedObjectsClient.get('config', '7.0.0-rc1');
|
||||
|
@ -169,6 +175,7 @@ describe('createOrUpgradeSavedConfig()', () => {
|
|||
version: '7.0.0',
|
||||
buildNum: 70099,
|
||||
log: logger,
|
||||
handleWriteErrors: false,
|
||||
});
|
||||
|
||||
const config700 = await savedObjectsClient.get('config', '7.0.0');
|
||||
|
@ -197,6 +204,7 @@ describe('createOrUpgradeSavedConfig()', () => {
|
|||
version: '6.2.3-rc1',
|
||||
buildNum: 62310,
|
||||
log: logger,
|
||||
handleWriteErrors: false,
|
||||
});
|
||||
|
||||
const config623rc1 = await savedObjectsClient.get('config', '6.2.3-rc1');
|
||||
|
|
|
@ -17,16 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
IUiSettingsClient,
|
||||
UiSettingsClient,
|
||||
UiSettingsServiceOptions,
|
||||
} from './ui_settings_client';
|
||||
export { UiSettingsClient, UiSettingsServiceOptions } from './ui_settings_client';
|
||||
|
||||
export { config } from './ui_settings_config';
|
||||
export { UiSettingsService } from './ui_settings_service';
|
||||
|
||||
export {
|
||||
UiSettingsServiceSetup,
|
||||
IUiSettingsClient,
|
||||
UiSettingsParams,
|
||||
UiSettingsService,
|
||||
InternalUiSettingsServiceSetup,
|
||||
UiSettingsType,
|
||||
} from './ui_settings_service';
|
||||
UserProvidedValues,
|
||||
} from './types';
|
||||
|
|
|
@ -17,20 +17,24 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { UnwrapPromise } from '@kbn/utility-types';
|
||||
import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/server';
|
||||
|
||||
import KbnServer from '../../../../../server/kbn_server';
|
||||
import { createTestServers } from '../../../../../../test_utils/kbn_server';
|
||||
import { CallCluster } from '../../../../../../legacy/core_plugins/elasticsearch';
|
||||
import {
|
||||
createTestServers,
|
||||
TestElasticsearchUtils,
|
||||
TestKibanaUtils,
|
||||
TestUtils,
|
||||
} from '../../../../../test_utils/kbn_server';
|
||||
import { CallCluster } from '../../../../../legacy/core_plugins/elasticsearch';
|
||||
|
||||
let kbnServer: KbnServer;
|
||||
let servers: ReturnType<typeof createTestServers>;
|
||||
let esServer: UnwrapPromise<ReturnType<typeof servers['startES']>>;
|
||||
let kbn: UnwrapPromise<ReturnType<typeof servers['startKibana']>>;
|
||||
let servers: TestUtils;
|
||||
let esServer: TestElasticsearchUtils;
|
||||
let kbn: TestKibanaUtils;
|
||||
|
||||
let kbnServer: TestKibanaUtils['kbnServer'];
|
||||
|
||||
interface AllServices {
|
||||
kbnServer: KbnServer;
|
||||
kbnServer: TestKibanaUtils['kbnServer'];
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
callCluster: CallCluster;
|
||||
uiSettings: IUiSettingsClient;
|
61
src/core/server/ui_settings/routes/delete.ts
Normal file
61
src/core/server/ui_settings/routes/delete.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
import { IRouter } from '../../http';
|
||||
import { SavedObjectsErrorHelpers } from '../../saved_objects';
|
||||
import { CannotOverrideError } from '../ui_settings_errors';
|
||||
|
||||
const validate = {
|
||||
params: schema.object({
|
||||
key: schema.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
export function registerDeleteRoute(router: IRouter) {
|
||||
router.delete(
|
||||
{ path: '/api/kibana/settings/{key}', validate },
|
||||
async (context, request, response) => {
|
||||
try {
|
||||
const uiSettingsClient = context.core.uiSettings.client;
|
||||
|
||||
await uiSettingsClient.remove(request.params.key);
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
settings: await uiSettingsClient.getUserProvided(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) {
|
||||
return response.customError({
|
||||
body: error,
|
||||
statusCode: error.output.statusCode,
|
||||
});
|
||||
}
|
||||
|
||||
if (error instanceof CannotOverrideError) {
|
||||
return response.badRequest({ body: error });
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
45
src/core/server/ui_settings/routes/get.ts
Normal file
45
src/core/server/ui_settings/routes/get.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 { IRouter } from '../../http';
|
||||
import { SavedObjectsErrorHelpers } from '../../saved_objects';
|
||||
|
||||
export function registerGetRoute(router: IRouter) {
|
||||
router.get(
|
||||
{ path: '/api/kibana/settings', validate: false },
|
||||
async (context, request, response) => {
|
||||
try {
|
||||
const uiSettingsClient = context.core.uiSettings.client;
|
||||
return response.ok({
|
||||
body: {
|
||||
settings: await uiSettingsClient.getUserProvided(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) {
|
||||
return response.customError({
|
||||
body: error,
|
||||
statusCode: error.output.statusCode,
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
31
src/core/server/ui_settings/routes/index.ts
Normal file
31
src/core/server/ui_settings/routes/index.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { IRouter } from 'src/core/server';
|
||||
|
||||
import { registerDeleteRoute } from './delete';
|
||||
import { registerGetRoute } from './get';
|
||||
import { registerSetManyRoute } from './set_many';
|
||||
import { registerSetRoute } from './set';
|
||||
|
||||
export function registerRoutes(router: IRouter) {
|
||||
registerGetRoute(router);
|
||||
registerDeleteRoute(router);
|
||||
registerSetRoute(router);
|
||||
registerSetManyRoute(router);
|
||||
}
|
67
src/core/server/ui_settings/routes/set.ts
Normal file
67
src/core/server/ui_settings/routes/set.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
import { IRouter } from '../../http';
|
||||
import { SavedObjectsErrorHelpers } from '../../saved_objects';
|
||||
import { CannotOverrideError } from '../ui_settings_errors';
|
||||
|
||||
const validate = {
|
||||
params: schema.object({
|
||||
key: schema.string(),
|
||||
}),
|
||||
body: schema.object({
|
||||
value: schema.any(),
|
||||
}),
|
||||
};
|
||||
|
||||
export function registerSetRoute(router: IRouter) {
|
||||
router.post(
|
||||
{ path: '/api/kibana/settings/{key}', validate },
|
||||
async (context, request, response) => {
|
||||
try {
|
||||
const uiSettingsClient = context.core.uiSettings.client;
|
||||
|
||||
const { key } = request.params;
|
||||
const { value } = request.body;
|
||||
|
||||
await uiSettingsClient.set(key, value);
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
settings: await uiSettingsClient.getUserProvided(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) {
|
||||
return response.customError({
|
||||
body: error,
|
||||
statusCode: error.output.statusCode,
|
||||
});
|
||||
}
|
||||
|
||||
if (error instanceof CannotOverrideError) {
|
||||
return response.badRequest({ body: error });
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
60
src/core/server/ui_settings/routes/set_many.ts
Normal file
60
src/core/server/ui_settings/routes/set_many.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
import { IRouter } from '../../http';
|
||||
import { SavedObjectsErrorHelpers } from '../../saved_objects';
|
||||
import { CannotOverrideError } from '../ui_settings_errors';
|
||||
|
||||
const validate = {
|
||||
body: schema.object({
|
||||
changes: schema.object({}, { allowUnknowns: true }),
|
||||
}),
|
||||
};
|
||||
|
||||
export function registerSetManyRoute(router: IRouter) {
|
||||
router.post({ path: '/api/kibana/settings', validate }, async (context, request, response) => {
|
||||
try {
|
||||
const uiSettingsClient = context.core.uiSettings.client;
|
||||
|
||||
const { changes } = request.body;
|
||||
|
||||
await uiSettingsClient.setMany(changes);
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
settings: await uiSettingsClient.getUserProvided(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) {
|
||||
return response.customError({
|
||||
body: error,
|
||||
statusCode: error.output.statusCode,
|
||||
});
|
||||
}
|
||||
|
||||
if (error instanceof CannotOverrideError) {
|
||||
return response.badRequest({ body: error });
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
141
src/core/server/ui_settings/types.ts
Normal file
141
src/core/server/ui_settings/types.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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 { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types';
|
||||
/**
|
||||
* Server-side client that provides access to the advanced settings stored in elasticsearch.
|
||||
* The settings provide control over the behavior of the Kibana application.
|
||||
* For example, a user can specify how to display numeric or date fields.
|
||||
* Users can adjust the settings via Management UI.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface IUiSettingsClient {
|
||||
/**
|
||||
* Returns registered uiSettings values {@link UiSettingsParams}
|
||||
*/
|
||||
getRegistered: () => Readonly<Record<string, UiSettingsParams>>;
|
||||
/**
|
||||
* Retrieves uiSettings values set by the user with fallbacks to default values if not specified.
|
||||
*/
|
||||
get: <T extends SavedObjectAttribute = any>(key: string) => Promise<T>;
|
||||
/**
|
||||
* Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified.
|
||||
*/
|
||||
getAll: <T extends SavedObjectAttribute = any>() => Promise<Record<string, T>>;
|
||||
/**
|
||||
* Retrieves a set of all uiSettings values set by the user.
|
||||
*/
|
||||
getUserProvided: <T extends SavedObjectAttribute = any>() => Promise<
|
||||
Record<string, UserProvidedValues<T>>
|
||||
>;
|
||||
/**
|
||||
* Writes multiple uiSettings values and marks them as set by the user.
|
||||
*/
|
||||
setMany: <T extends SavedObjectAttribute = any>(changes: Record<string, T>) => Promise<void>;
|
||||
/**
|
||||
* Writes uiSettings value and marks it as set by the user.
|
||||
*/
|
||||
set: <T extends SavedObjectAttribute = any>(key: string, value: T) => Promise<void>;
|
||||
/**
|
||||
* Removes uiSettings value by key.
|
||||
*/
|
||||
remove: (key: string) => Promise<void>;
|
||||
/**
|
||||
* Removes multiple uiSettings values by keys.
|
||||
*/
|
||||
removeMany: (keys: string[]) => Promise<void>;
|
||||
/**
|
||||
* Shows whether the uiSettings value set by the user.
|
||||
*/
|
||||
isOverridden: (key: string) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the values explicitly set by user.
|
||||
* @public
|
||||
* */
|
||||
export interface UserProvidedValues<T extends SavedObjectAttribute = any> {
|
||||
userValue?: T;
|
||||
isOverridden?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI element type to represent the settings.
|
||||
* @public
|
||||
* */
|
||||
export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string';
|
||||
|
||||
/**
|
||||
* UiSettings parameters defined by the plugins.
|
||||
* @public
|
||||
* */
|
||||
export interface UiSettingsParams {
|
||||
/** title in the UI */
|
||||
name?: string;
|
||||
/** default value to fall back to if a user doesn't provide any */
|
||||
value?: SavedObjectAttribute;
|
||||
/** description provided to a user in UI */
|
||||
description?: string;
|
||||
/** used to group the configured setting in the UI */
|
||||
category?: string[];
|
||||
/** array of permitted values for this setting */
|
||||
options?: string[];
|
||||
/** text labels for 'select' type UI element */
|
||||
optionLabels?: Record<string, string>;
|
||||
/** a flag indicating whether new value applying requires page reloading */
|
||||
requiresPageReload?: boolean;
|
||||
/** a flag indicating that value cannot be changed */
|
||||
readonly?: boolean;
|
||||
/** defines a type of UI element {@link UiSettingsType} */
|
||||
type?: UiSettingsType;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface InternalUiSettingsServiceSetup {
|
||||
/**
|
||||
* Sets settings with default values for the uiSettings.
|
||||
* @param settings
|
||||
*/
|
||||
register(settings: Record<string, UiSettingsParams>): void;
|
||||
/**
|
||||
* Creates uiSettings client with provided *scoped* saved objects client {@link IUiSettingsClient}
|
||||
* @param savedObjectsClient
|
||||
*/
|
||||
asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface UiSettingsServiceSetup {
|
||||
/**
|
||||
* Sets settings with default values for the uiSettings.
|
||||
* @param settings
|
||||
*
|
||||
* @example
|
||||
* setup(core: CoreSetup){
|
||||
* core.uiSettings.register([{
|
||||
* foo: {
|
||||
* name: i18n.translate('my foo settings'),
|
||||
* value: true,
|
||||
* description: 'add some awesomeness',
|
||||
* },
|
||||
* }]);
|
||||
* }
|
||||
*/
|
||||
register(settings: Record<string, UiSettingsParams>): void;
|
||||
}
|
|
@ -24,6 +24,7 @@ import sinon from 'sinon';
|
|||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
|
||||
import { UiSettingsClient } from './ui_settings_client';
|
||||
import { CannotOverrideError } from './ui_settings_errors';
|
||||
import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config';
|
||||
import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub';
|
||||
|
||||
|
@ -119,7 +120,12 @@ describe('ui settings', () => {
|
|||
|
||||
await uiSettings.setMany({ foo: 'bar' });
|
||||
sinon.assert.calledTwice(savedObjectsClient.update);
|
||||
|
||||
sinon.assert.calledOnce(createOrUpgradeSavedConfig);
|
||||
sinon.assert.calledWith(
|
||||
createOrUpgradeSavedConfig,
|
||||
sinon.match({ handleWriteErrors: false })
|
||||
);
|
||||
});
|
||||
|
||||
it('only tried to auto create once and throws NotFound', async () => {
|
||||
|
@ -135,9 +141,14 @@ describe('ui settings', () => {
|
|||
|
||||
sinon.assert.calledTwice(savedObjectsClient.update);
|
||||
sinon.assert.calledOnce(createOrUpgradeSavedConfig);
|
||||
|
||||
sinon.assert.calledWith(
|
||||
createOrUpgradeSavedConfig,
|
||||
sinon.match({ handleWriteErrors: false })
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error if any key is overridden', async () => {
|
||||
it('throws CannotOverrideError if the key is overridden', async () => {
|
||||
const { uiSettings } = setup({
|
||||
overrides: {
|
||||
foo: 'bar',
|
||||
|
@ -150,6 +161,7 @@ describe('ui settings', () => {
|
|||
foo: 'baz',
|
||||
});
|
||||
} catch (error) {
|
||||
expect(error).to.be.a(CannotOverrideError);
|
||||
expect(error.message).to.be('Unable to update "foo" because it is overridden');
|
||||
}
|
||||
});
|
||||
|
@ -167,7 +179,7 @@ describe('ui settings', () => {
|
|||
assertUpdateQuery({ one: 'value' });
|
||||
});
|
||||
|
||||
it('throws an error if the key is overridden', async () => {
|
||||
it('throws CannotOverrideError if the key is overridden', async () => {
|
||||
const { uiSettings } = setup({
|
||||
overrides: {
|
||||
foo: 'bar',
|
||||
|
@ -177,6 +189,7 @@ describe('ui settings', () => {
|
|||
try {
|
||||
await uiSettings.set('foo', 'baz');
|
||||
} catch (error) {
|
||||
expect(error).to.be.a(CannotOverrideError);
|
||||
expect(error.message).to.be('Unable to update "foo" because it is overridden');
|
||||
}
|
||||
});
|
||||
|
@ -194,7 +207,7 @@ describe('ui settings', () => {
|
|||
assertUpdateQuery({ one: null });
|
||||
});
|
||||
|
||||
it('throws an error if the key is overridden', async () => {
|
||||
it('throws CannotOverrideError if the key is overridden', async () => {
|
||||
const { uiSettings } = setup({
|
||||
overrides: {
|
||||
foo: 'bar',
|
||||
|
@ -204,6 +217,7 @@ describe('ui settings', () => {
|
|||
try {
|
||||
await uiSettings.remove('foo');
|
||||
} catch (error) {
|
||||
expect(error).to.be.a(CannotOverrideError);
|
||||
expect(error.message).to.be('Unable to update "foo" because it is overridden');
|
||||
}
|
||||
});
|
||||
|
@ -227,7 +241,7 @@ describe('ui settings', () => {
|
|||
assertUpdateQuery({ one: null, two: null, three: null });
|
||||
});
|
||||
|
||||
it('throws an error if any key is overridden', async () => {
|
||||
it('throws CannotOverrideError if any key is overridden', async () => {
|
||||
const { uiSettings } = setup({
|
||||
overrides: {
|
||||
foo: 'bar',
|
||||
|
@ -237,18 +251,18 @@ describe('ui settings', () => {
|
|||
try {
|
||||
await uiSettings.setMany({ baz: 'baz', foo: 'foo' });
|
||||
} catch (error) {
|
||||
expect(error).to.be.a(CannotOverrideError);
|
||||
expect(error.message).to.be('Unable to update "foo" because it is overridden');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getDefaults()', () => {
|
||||
it('returns the defaults passed to the constructor', () => {
|
||||
describe('#getRegistered()', () => {
|
||||
it('returns the registered settings passed to the constructor', () => {
|
||||
const value = chance.word();
|
||||
const { uiSettings } = setup({ defaults: { key: { value } } });
|
||||
expect(uiSettings.getDefaults()).to.eql({
|
||||
key: { value },
|
||||
});
|
||||
const defaults = { key: { value } };
|
||||
const { uiSettings } = setup({ defaults });
|
||||
expect(uiSettings.getRegistered()).to.be(defaults);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -284,31 +298,48 @@ describe('ui settings', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it.skip('returns an empty object on NotFound responses', async () => {
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
it('automatically creates the savedConfig if it is missing and returns empty object', async () => {
|
||||
const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup();
|
||||
savedObjectsClient.get
|
||||
.onFirstCall()
|
||||
.throws(savedObjectsClientErrors.createGenericNotFoundError())
|
||||
.onSecondCall()
|
||||
.returns({ attributes: {} });
|
||||
|
||||
const error = savedObjectsClientErrors.createGenericNotFoundError();
|
||||
savedObjectsClient.get.throws(error);
|
||||
expect(await uiSettings.getUserProvided()).to.eql({});
|
||||
|
||||
expect(await uiSettings.getUserProvided({})).to.eql({});
|
||||
sinon.assert.calledTwice(savedObjectsClient.get);
|
||||
|
||||
sinon.assert.calledOnce(createOrUpgradeSavedConfig);
|
||||
sinon.assert.calledWith(createOrUpgradeSavedConfig, sinon.match({ handleWriteErrors: true }));
|
||||
});
|
||||
|
||||
it('returns result of savedConfig creation in case of notFound error', async () => {
|
||||
const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup();
|
||||
createOrUpgradeSavedConfig.resolves({ foo: 'bar ' });
|
||||
savedObjectsClient.get.throws(savedObjectsClientErrors.createGenericNotFoundError());
|
||||
|
||||
expect(await uiSettings.getUserProvided()).to.eql({ foo: { userValue: 'bar ' } });
|
||||
});
|
||||
|
||||
it('returns an empty object on Forbidden responses', async () => {
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup();
|
||||
|
||||
const error = savedObjectsClientErrors.decorateForbiddenError(new Error());
|
||||
savedObjectsClient.get.throws(error);
|
||||
|
||||
expect(await uiSettings.getUserProvided()).to.eql({});
|
||||
sinon.assert.notCalled(createOrUpgradeSavedConfig);
|
||||
});
|
||||
|
||||
it('returns an empty object on EsUnavailable responses', async () => {
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup();
|
||||
|
||||
const error = savedObjectsClientErrors.decorateEsUnavailableError(new Error());
|
||||
savedObjectsClient.get.throws(error);
|
||||
|
||||
expect(await uiSettings.getUserProvided()).to.eql({});
|
||||
sinon.assert.notCalled(createOrUpgradeSavedConfig);
|
||||
});
|
||||
|
||||
it('throws Unauthorized errors', async () => {
|
||||
|
@ -346,6 +377,7 @@ describe('ui settings', () => {
|
|||
|
||||
const overrides = {
|
||||
foo: 'bar',
|
||||
baz: null,
|
||||
};
|
||||
|
||||
const { uiSettings } = setup({ esDocSource, overrides });
|
||||
|
@ -357,57 +389,7 @@ describe('ui settings', () => {
|
|||
userValue: 'bar',
|
||||
isOverridden: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getRaw()', () => {
|
||||
it('pulls user configuration from ES', async () => {
|
||||
const esDocSource = {};
|
||||
const { uiSettings, assertGetQuery } = setup({ esDocSource });
|
||||
await uiSettings.getRaw();
|
||||
assertGetQuery();
|
||||
});
|
||||
|
||||
it(`without user configuration it's equal to the defaults`, async () => {
|
||||
const esDocSource = {};
|
||||
const defaults = { key: { value: chance.word() } };
|
||||
const { uiSettings } = setup({ esDocSource, defaults });
|
||||
const result = await uiSettings.getRaw();
|
||||
expect(result).to.eql(defaults);
|
||||
});
|
||||
|
||||
it(`user configuration gets merged with defaults`, async () => {
|
||||
const esDocSource = { foo: 'bar' };
|
||||
const defaults = { key: { value: chance.word() } };
|
||||
const { uiSettings } = setup({ esDocSource, defaults });
|
||||
const result = await uiSettings.getRaw();
|
||||
|
||||
expect(result).to.eql({
|
||||
foo: {
|
||||
userValue: 'bar',
|
||||
},
|
||||
key: {
|
||||
value: defaults.key.value,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('includes the values for overridden keys', async () => {
|
||||
const esDocSource = { foo: 'bar' };
|
||||
const defaults = { key: { value: chance.word() } };
|
||||
const overrides = { foo: true };
|
||||
const { uiSettings } = setup({ esDocSource, defaults, overrides });
|
||||
const result = await uiSettings.getRaw();
|
||||
|
||||
expect(result).to.eql({
|
||||
foo: {
|
||||
userValue: true,
|
||||
isOverridden: true,
|
||||
},
|
||||
key: {
|
||||
value: defaults.key.value,
|
||||
},
|
||||
baz: { isOverridden: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -545,22 +527,4 @@ describe('ui settings', () => {
|
|||
expect(uiSettings.isOverridden('bar')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#assertUpdateAllowed()', () => {
|
||||
it('returns false if no overrides defined', () => {
|
||||
const { uiSettings } = setup();
|
||||
expect(uiSettings.assertUpdateAllowed('foo')).to.be(undefined);
|
||||
});
|
||||
it('throws 400 Boom error when keys is overridden', () => {
|
||||
const { uiSettings } = setup({ overrides: { foo: true } });
|
||||
expect(() => uiSettings.assertUpdateAllowed('foo')).to.throwError(error => {
|
||||
expect(error).to.have.property(
|
||||
'message',
|
||||
'Unable to update "foo" because it is overridden'
|
||||
);
|
||||
expect(error).to.have.property('isBoom', true);
|
||||
expect(error.output).to.have.property('statusCode', 400);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { defaultsDeep } from 'lodash';
|
||||
import Boom from 'boom';
|
||||
|
||||
import { SavedObjectsErrorHelpers } from '../saved_objects';
|
||||
import { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types';
|
||||
import { Logger } from '../logging';
|
||||
import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
|
||||
import { UiSettingsParams } from './ui_settings_service';
|
||||
import { IUiSettingsClient, UiSettingsParams } from './types';
|
||||
import { CannotOverrideError } from './ui_settings_errors';
|
||||
|
||||
export interface UiSettingsServiceOptions {
|
||||
type: string;
|
||||
|
@ -49,52 +50,6 @@ type UiSettingsRawValue = UiSettingsParams & UserProvidedValue;
|
|||
type UserProvided<T extends SavedObjectAttribute = any> = Record<string, UserProvidedValue<T>>;
|
||||
type UiSettingsRaw = Record<string, UiSettingsRawValue>;
|
||||
|
||||
/**
|
||||
* Service that provides access to the UiSettings stored in elasticsearch.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface IUiSettingsClient {
|
||||
/**
|
||||
* Returns uiSettings default values {@link UiSettingsParams}
|
||||
*/
|
||||
getDefaults: () => Record<string, UiSettingsParams>;
|
||||
/**
|
||||
* Retrieves uiSettings values set by the user with fallbacks to default values if not specified.
|
||||
*/
|
||||
get: <T extends SavedObjectAttribute = any>(key: string) => Promise<T>;
|
||||
/**
|
||||
* Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified.
|
||||
*/
|
||||
getAll: <T extends SavedObjectAttribute = any>() => Promise<Record<string, T>>;
|
||||
/**
|
||||
* Retrieves a set of all uiSettings values set by the user.
|
||||
*/
|
||||
getUserProvided: <T extends SavedObjectAttribute = any>() => Promise<
|
||||
Record<string, { userValue?: T; isOverridden?: boolean }>
|
||||
>;
|
||||
/**
|
||||
* Writes multiple uiSettings values and marks them as set by the user.
|
||||
*/
|
||||
setMany: <T extends SavedObjectAttribute = any>(changes: Record<string, T>) => Promise<void>;
|
||||
/**
|
||||
* Writes uiSettings value and marks it as set by the user.
|
||||
*/
|
||||
set: <T extends SavedObjectAttribute = any>(key: string, value: T) => Promise<void>;
|
||||
/**
|
||||
* Removes uiSettings value by key.
|
||||
*/
|
||||
remove: (key: string) => Promise<void>;
|
||||
/**
|
||||
* Removes multiple uiSettings values by keys.
|
||||
*/
|
||||
removeMany: (keys: string[]) => Promise<void>;
|
||||
/**
|
||||
* Shows whether the uiSettings value set by the user.
|
||||
*/
|
||||
isOverridden: (key: string) => boolean;
|
||||
}
|
||||
|
||||
export class UiSettingsClient implements IUiSettingsClient {
|
||||
private readonly type: UiSettingsServiceOptions['type'];
|
||||
private readonly id: UiSettingsServiceOptions['id'];
|
||||
|
@ -116,7 +71,7 @@ export class UiSettingsClient implements IUiSettingsClient {
|
|||
this.log = log;
|
||||
}
|
||||
|
||||
getDefaults() {
|
||||
getRegistered() {
|
||||
return this.defaults;
|
||||
}
|
||||
|
||||
|
@ -138,19 +93,11 @@ export class UiSettingsClient implements IUiSettingsClient {
|
|||
);
|
||||
}
|
||||
|
||||
// NOTE: should be a private method
|
||||
async getRaw(): Promise<UiSettingsRaw> {
|
||||
const userProvided = await this.getUserProvided();
|
||||
return defaultsDeep(userProvided, this.defaults);
|
||||
}
|
||||
|
||||
async getUserProvided<T extends SavedObjectAttribute = any>(
|
||||
options: ReadOptions = {}
|
||||
): Promise<UserProvided<T>> {
|
||||
async getUserProvided<T extends SavedObjectAttribute = any>(): Promise<UserProvided<T>> {
|
||||
const userProvided: UserProvided = {};
|
||||
|
||||
// write the userValue for each key stored in the saved object that is not overridden
|
||||
for (const [key, userValue] of Object.entries(await this.read(options))) {
|
||||
for (const [key, userValue] of Object.entries(await this.read())) {
|
||||
if (userValue !== null && !this.isOverridden(key)) {
|
||||
userProvided[key] = {
|
||||
userValue,
|
||||
|
@ -192,13 +139,17 @@ export class UiSettingsClient implements IUiSettingsClient {
|
|||
return this.overrides.hasOwnProperty(key);
|
||||
}
|
||||
|
||||
// NOTE: should be private method
|
||||
assertUpdateAllowed(key: string) {
|
||||
private assertUpdateAllowed(key: string) {
|
||||
if (this.isOverridden(key)) {
|
||||
throw Boom.badRequest(`Unable to update "${key}" because it is overridden`);
|
||||
throw new CannotOverrideError(`Unable to update "${key}" because it is overridden`);
|
||||
}
|
||||
}
|
||||
|
||||
private async getRaw(): Promise<UiSettingsRaw> {
|
||||
const userProvided = await this.getUserProvided();
|
||||
return defaultsDeep(userProvided, this.defaults);
|
||||
}
|
||||
|
||||
private async write<T extends SavedObjectAttribute = any>({
|
||||
changes,
|
||||
autoCreateOrUpgradeIfMissing = true,
|
||||
|
@ -213,8 +164,7 @@ export class UiSettingsClient implements IUiSettingsClient {
|
|||
try {
|
||||
await this.savedObjectsClient.update(this.type, this.id, changes);
|
||||
} catch (error) {
|
||||
const { isNotFoundError } = this.savedObjectsClient.errors;
|
||||
if (!isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) {
|
||||
if (!SavedObjectsErrorHelpers.isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
@ -223,6 +173,7 @@ export class UiSettingsClient implements IUiSettingsClient {
|
|||
version: this.id,
|
||||
buildNum: this.buildNum,
|
||||
log: this.log,
|
||||
handleWriteErrors: false,
|
||||
});
|
||||
|
||||
await this.write({
|
||||
|
@ -236,37 +187,17 @@ export class UiSettingsClient implements IUiSettingsClient {
|
|||
ignore401Errors = false,
|
||||
autoCreateOrUpgradeIfMissing = true,
|
||||
}: ReadOptions = {}): Promise<Record<string, T>> {
|
||||
const {
|
||||
isConflictError,
|
||||
isNotFoundError,
|
||||
isForbiddenError,
|
||||
isNotAuthorizedError,
|
||||
} = this.savedObjectsClient.errors;
|
||||
|
||||
try {
|
||||
const resp = await this.savedObjectsClient.get(this.type, this.id);
|
||||
return resp.attributes;
|
||||
} catch (error) {
|
||||
if (isNotFoundError(error) && autoCreateOrUpgradeIfMissing) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(error) && autoCreateOrUpgradeIfMissing) {
|
||||
const failedUpgradeAttributes = await createOrUpgradeSavedConfig<T>({
|
||||
savedObjectsClient: this.savedObjectsClient,
|
||||
version: this.id,
|
||||
buildNum: this.buildNum,
|
||||
log: this.log,
|
||||
onWriteError(writeError, attributes) {
|
||||
if (isConflictError(writeError)) {
|
||||
// trigger `!failedUpgradeAttributes` check below, since another
|
||||
// request caused the uiSettings object to be created so we can
|
||||
// just re-read
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNotAuthorizedError(writeError) || isForbiddenError(writeError)) {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
throw writeError;
|
||||
},
|
||||
handleWriteErrors: true,
|
||||
});
|
||||
|
||||
if (!failedUpgradeAttributes) {
|
||||
|
|
31
src/core/server/ui_settings/ui_settings_errors.ts
Normal file
31
src/core/server/ui_settings/ui_settings_errors.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 class CannotOverrideError extends Error {
|
||||
public cause?: Error;
|
||||
|
||||
constructor(message: string, cause?: Error) {
|
||||
super(message);
|
||||
this.cause = cause;
|
||||
|
||||
// Set the prototype explicitly, see:
|
||||
// https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
||||
Object.setPrototypeOf(this, CannotOverrideError.prototype);
|
||||
}
|
||||
}
|
|
@ -17,12 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IUiSettingsClient } from './ui_settings_client';
|
||||
import { InternalUiSettingsServiceSetup } from './ui_settings_service';
|
||||
import { IUiSettingsClient, InternalUiSettingsServiceSetup } from './types';
|
||||
|
||||
const createClientMock = () => {
|
||||
const mocked: jest.Mocked<IUiSettingsClient> = {
|
||||
getDefaults: jest.fn(),
|
||||
getRegistered: jest.fn(),
|
||||
get: jest.fn(),
|
||||
getAll: jest.fn(),
|
||||
getUserProvided: jest.fn(),
|
||||
|
@ -38,7 +37,7 @@ const createClientMock = () => {
|
|||
|
||||
const createSetupMock = () => {
|
||||
const mocked: jest.Mocked<InternalUiSettingsServiceSetup> = {
|
||||
setDefaults: jest.fn(),
|
||||
register: jest.fn(),
|
||||
asScopedToClient: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -48,6 +47,6 @@ const createSetupMock = () => {
|
|||
};
|
||||
|
||||
export const uiSettingsServiceMock = {
|
||||
createSetup: createSetupMock,
|
||||
createSetupContract: createSetupMock,
|
||||
createClient: createClientMock,
|
||||
};
|
||||
|
|
|
@ -52,6 +52,14 @@ afterEach(() => {
|
|||
describe('uiSettings', () => {
|
||||
describe('#setup', () => {
|
||||
describe('#asScopedToClient', () => {
|
||||
it('passes saved object type "config" to UiSettingsClient', async () => {
|
||||
const service = new UiSettingsService(coreContext);
|
||||
const setup = await service.setup(setupDeps);
|
||||
setup.asScopedToClient(savedObjectsClient);
|
||||
expect(MockUiSettingsClientConstructor).toBeCalledTimes(1);
|
||||
expect(MockUiSettingsClientConstructor.mock.calls[0][0].type).toBe('config');
|
||||
});
|
||||
|
||||
it('passes overrides to UiSettingsClient', async () => {
|
||||
const service = new UiSettingsService(coreContext);
|
||||
const setup = await service.setup(setupDeps);
|
||||
|
@ -86,7 +94,7 @@ describe('uiSettings', () => {
|
|||
const service = new UiSettingsService(coreContext);
|
||||
const setup = await service.setup(setupDeps);
|
||||
|
||||
setup.setDefaults(defaults);
|
||||
setup.register(defaults);
|
||||
setup.asScopedToClient(savedObjectsClient);
|
||||
expect(MockUiSettingsClientConstructor).toBeCalledTimes(1);
|
||||
|
||||
|
@ -95,13 +103,13 @@ describe('uiSettings', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#setDefaults', () => {
|
||||
it('throws if set defaults for the same key twice', async () => {
|
||||
describe('#register', () => {
|
||||
it('throws if registers the same key twice', async () => {
|
||||
const service = new UiSettingsService(coreContext);
|
||||
const setup = await service.setup(setupDeps);
|
||||
setup.setDefaults(defaults);
|
||||
expect(() => setup.setDefaults(defaults)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"uiSettings defaults for key [foo] has been already set"`
|
||||
setup.register(defaults);
|
||||
expect(() => setup.register(defaults)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"uiSettings for the key [foo] has been already registered"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,58 +26,16 @@ import { Logger } from '../logging';
|
|||
import { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types';
|
||||
import { InternalHttpServiceSetup } from '../http';
|
||||
import { UiSettingsConfigType } from './ui_settings_config';
|
||||
import { IUiSettingsClient, UiSettingsClient } from './ui_settings_client';
|
||||
import { UiSettingsClient } from './ui_settings_client';
|
||||
import { InternalUiSettingsServiceSetup, UiSettingsParams } from './types';
|
||||
import { mapToObject } from '../../utils/';
|
||||
|
||||
import { registerRoutes } from './routes';
|
||||
|
||||
interface SetupDeps {
|
||||
http: InternalHttpServiceSetup;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI element type to represent the settings.
|
||||
* @public
|
||||
* */
|
||||
export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string';
|
||||
|
||||
/**
|
||||
* UiSettings parameters defined by the plugins.
|
||||
* @public
|
||||
* */
|
||||
export interface UiSettingsParams {
|
||||
/** title in the UI */
|
||||
name: string;
|
||||
/** default value to fall back to if a user doesn't provide any */
|
||||
value: SavedObjectAttribute;
|
||||
/** description provided to a user in UI */
|
||||
description: string;
|
||||
/** used to group the configured setting in the UI */
|
||||
category: string[];
|
||||
/** a range of valid values */
|
||||
options?: string[];
|
||||
/** text labels for 'select' type UI element */
|
||||
optionLabels?: Record<string, string>;
|
||||
/** a flag indicating whether new value applying requires page reloading */
|
||||
requiresPageReload?: boolean;
|
||||
/** a flag indicating that value cannot be changed */
|
||||
readonly?: boolean;
|
||||
/** defines a type of UI element {@link UiSettingsType} */
|
||||
type?: UiSettingsType;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface InternalUiSettingsServiceSetup {
|
||||
/**
|
||||
* Sets the parameters with default values for the uiSettings.
|
||||
* @param values
|
||||
*/
|
||||
setDefaults(values: Record<string, UiSettingsParams>): void;
|
||||
/**
|
||||
* Creates uiSettings client with provided *scoped* saved objects client {@link IUiSettingsClient}
|
||||
* @param values
|
||||
*/
|
||||
asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class UiSettingsService implements CoreService<InternalUiSettingsServiceSetup> {
|
||||
private readonly log: Logger;
|
||||
|
@ -90,12 +48,13 @@ export class UiSettingsService implements CoreService<InternalUiSettingsServiceS
|
|||
}
|
||||
|
||||
public async setup(deps: SetupDeps): Promise<InternalUiSettingsServiceSetup> {
|
||||
registerRoutes(deps.http.createRouter(''));
|
||||
this.log.debug('Setting up ui settings service');
|
||||
const overrides = await this.getOverrides(deps);
|
||||
const { version, buildNum } = this.coreContext.env.packageInfo;
|
||||
|
||||
return {
|
||||
setDefaults: this.setDefaults.bind(this),
|
||||
register: this.register.bind(this),
|
||||
asScopedToClient: (savedObjectsClient: SavedObjectsClientContract) => {
|
||||
return new UiSettingsClient({
|
||||
type: 'config',
|
||||
|
@ -114,10 +73,10 @@ export class UiSettingsService implements CoreService<InternalUiSettingsServiceS
|
|||
|
||||
public async stop() {}
|
||||
|
||||
private setDefaults(values: Record<string, UiSettingsParams> = {}) {
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
private register(settings: Record<string, UiSettingsParams> = {}) {
|
||||
Object.entries(settings).forEach(([key, value]) => {
|
||||
if (this.uiSettingsDefaults.has(key)) {
|
||||
throw new Error(`uiSettings defaults for key [${key}] has been already set`);
|
||||
throw new Error(`uiSettings for the key [${key}] has been already registered`);
|
||||
}
|
||||
this.uiSettingsDefaults.set(key, value);
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@ describe('default route provider', () => {
|
|||
}
|
||||
throw Error(`unsupported ui setting: ${key}`);
|
||||
},
|
||||
getDefaults: () => {
|
||||
getRegistered: () => {
|
||||
return {
|
||||
defaultRoute: {
|
||||
value: '/app/kibana',
|
||||
|
|
|
@ -40,7 +40,7 @@ export function setupDefaultRouteProvider(server: Legacy.Server) {
|
|||
`Ignoring configured default route of '${defaultRoute}', as it is malformed.`
|
||||
);
|
||||
|
||||
const fallbackRoute = uiSettings.getDefaults().defaultRoute.value;
|
||||
const fallbackRoute = uiSettings.getRegistered().defaultRoute.value;
|
||||
|
||||
const qualifiedFallbackRoute = `${request.getBasePath()}${fallbackRoute}`;
|
||||
return qualifiedFallbackRoute;
|
||||
|
|
|
@ -69,7 +69,7 @@ describe('UiExports', function () {
|
|||
sandbox
|
||||
.stub(getUiSettingsServiceForRequestNS, 'getUiSettingsServiceForRequest')
|
||||
.returns({
|
||||
getDefaults: noop,
|
||||
getRegistered: noop,
|
||||
getUserProvided: noop
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,9 +28,9 @@ export function fieldFormatsMixin(kbnServer: any, server: Legacy.Server) {
|
|||
// for use outside of the request context, for special cases
|
||||
server.decorate('server', 'fieldFormatServiceFactory', async function(uiSettings) {
|
||||
const uiConfigs = await uiSettings.getAll();
|
||||
const uiSettingDefaults = uiSettings.getDefaults();
|
||||
Object.keys(uiSettingDefaults).forEach(key => {
|
||||
if (has(uiConfigs, key) && uiSettingDefaults[key].type === 'json') {
|
||||
const registeredUiSettings = uiSettings.getRegistered();
|
||||
Object.keys(registeredUiSettings).forEach(key => {
|
||||
if (has(uiConfigs, key) && registeredUiSettings[key].type === 'json') {
|
||||
uiConfigs[key] = JSON.parse(uiConfigs[key]);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -185,7 +185,7 @@ export function uiRenderMixin(kbnServer, server, config) {
|
|||
async function getUiSettings({ request, includeUserProvidedConfig }) {
|
||||
const uiSettings = request.getUiSettingsService();
|
||||
return props({
|
||||
defaults: uiSettings.getDefaults(),
|
||||
defaults: uiSettings.getRegistered(),
|
||||
user: includeUserProvidedConfig && uiSettings.getUserProvided()
|
||||
});
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ describe('uiSettingsMixin()', () => {
|
|||
newPlatform: {
|
||||
__internals: {
|
||||
uiSettings: {
|
||||
setDefaults: sinon.stub(),
|
||||
register: sinon.stub(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -93,9 +93,9 @@ describe('uiSettingsMixin()', () => {
|
|||
|
||||
it('passes uiSettingsDefaults to the new platform', () => {
|
||||
const { kbnServer } = setup();
|
||||
sinon.assert.calledOnce(kbnServer.newPlatform.__internals.uiSettings.setDefaults);
|
||||
sinon.assert.calledOnce(kbnServer.newPlatform.__internals.uiSettings.register);
|
||||
sinon.assert.calledWithExactly(
|
||||
kbnServer.newPlatform.__internals.uiSettings.setDefaults,
|
||||
kbnServer.newPlatform.__internals.uiSettings.register,
|
||||
uiSettingDefaults
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,55 +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 { Legacy } from 'kibana';
|
||||
import Joi from 'joi';
|
||||
|
||||
async function handleRequest(request: Legacy.Request) {
|
||||
const { key } = request.params;
|
||||
const { value } = request.payload as any;
|
||||
const uiSettings = request.getUiSettingsService();
|
||||
|
||||
await uiSettings.set(key, value);
|
||||
|
||||
return {
|
||||
settings: await uiSettings.getUserProvided(),
|
||||
};
|
||||
}
|
||||
|
||||
export const setRoute = {
|
||||
path: '/api/kibana/settings/{key}',
|
||||
method: 'POST',
|
||||
config: {
|
||||
validate: {
|
||||
params: Joi.object()
|
||||
.keys({
|
||||
key: Joi.string().required(),
|
||||
})
|
||||
.default(),
|
||||
|
||||
payload: Joi.object()
|
||||
.keys({
|
||||
value: Joi.any().required(),
|
||||
})
|
||||
.required(),
|
||||
},
|
||||
handler(request: Legacy.Request) {
|
||||
return handleRequest(request);
|
||||
},
|
||||
},
|
||||
};
|
|
@ -19,12 +19,6 @@
|
|||
|
||||
import { uiSettingsServiceFactory } from './ui_settings_service_factory';
|
||||
import { getUiSettingsServiceForRequest } from './ui_settings_service_for_request';
|
||||
import {
|
||||
deleteRoute,
|
||||
getRoute,
|
||||
setManyRoute,
|
||||
setRoute,
|
||||
} from './routes';
|
||||
|
||||
export function uiSettingsMixin(kbnServer, server) {
|
||||
const { uiSettingDefaults = {} } = kbnServer.uiExports;
|
||||
|
@ -43,7 +37,7 @@ export function uiSettingsMixin(kbnServer, server) {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
kbnServer.newPlatform.__internals.uiSettings.setDefaults(mergedUiSettingDefaults);
|
||||
kbnServer.newPlatform.__internals.uiSettings.register(mergedUiSettingDefaults);
|
||||
|
||||
server.decorate('server', 'uiSettingsServiceFactory', (options = {}) => {
|
||||
return uiSettingsServiceFactory(server, options);
|
||||
|
@ -58,9 +52,4 @@ export function uiSettingsMixin(kbnServer, server) {
|
|||
server.uiSettings has been removed, see https://github.com/elastic/kibana/pull/12243.
|
||||
`);
|
||||
});
|
||||
|
||||
server.route(deleteRoute);
|
||||
server.route(getRoute);
|
||||
server.route(setManyRoute);
|
||||
server.route(setRoute);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Client } from 'elasticsearch';
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
import {
|
||||
createLegacyEsTestCluster,
|
||||
|
@ -36,6 +36,7 @@ import { CliArgs, Env } from '../core/server/config';
|
|||
import { LegacyObjectToConfigAdapter } from '../core/server/legacy';
|
||||
import { Root } from '../core/server/root';
|
||||
import KbnServer from '../legacy/server/kbn_server';
|
||||
import { CallCluster } from '../legacy/core_plugins/elasticsearch';
|
||||
|
||||
type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put';
|
||||
|
||||
|
@ -144,6 +145,35 @@ export const request: Record<
|
|||
put: (root, path) => getSupertest(root, 'put', path),
|
||||
};
|
||||
|
||||
export interface TestElasticsearchServer {
|
||||
getStartTimeout: () => number;
|
||||
start: (esArgs: string[], esEnvVars: Record<string, string>) => Promise<void>;
|
||||
stop: () => Promise<void>;
|
||||
cleanup: () => Promise<void>;
|
||||
getClient: () => Client;
|
||||
getCallCluster: () => CallCluster;
|
||||
getUrl: () => string;
|
||||
}
|
||||
|
||||
export interface TestElasticsearchUtils {
|
||||
stop: () => Promise<void>;
|
||||
es: TestElasticsearchServer;
|
||||
hosts: string[];
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface TestKibanaUtils {
|
||||
root: Root;
|
||||
kbnServer: KbnServer;
|
||||
stop: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface TestUtils {
|
||||
startES: () => Promise<TestElasticsearchUtils>;
|
||||
startKibana: () => Promise<TestKibanaUtils>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the Root, including all of the core "legacy" plugins,
|
||||
* with default configuration tailored for unit tests, and starts es.
|
||||
|
@ -158,7 +188,7 @@ export function createTestServers({
|
|||
settings = {},
|
||||
}: {
|
||||
adjustTimeout: (timeout: number) => void;
|
||||
settings: {
|
||||
settings?: {
|
||||
es?: {
|
||||
license: 'oss' | 'basic' | 'gold' | 'trial';
|
||||
[key: string]: any;
|
||||
|
@ -179,7 +209,7 @@ export function createTestServers({
|
|||
*/
|
||||
users?: Array<{ username: string; password: string; roles: string[] }>;
|
||||
};
|
||||
}) {
|
||||
}): TestUtils {
|
||||
if (!adjustTimeout) {
|
||||
throw new Error('adjustTimeout is required in order to avoid flaky tests');
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ declare module 'kibana/server' {
|
|||
export class CorePluginBPlugin implements Plugin {
|
||||
public setup(core: CoreSetup, deps: {}) {
|
||||
const router = core.http.createRouter();
|
||||
router.get({ path: '/core_plugin_b/', validate: false }, async (context, req, res) => {
|
||||
router.get({ path: '/core_plugin_b', validate: false }, async (context, req, res) => {
|
||||
if (!context.pluginA) return res.internalError({ body: 'pluginA is disabled' });
|
||||
const response = await context.pluginA.ping();
|
||||
return res.ok({ body: `Pong via plugin A: ${response}` });
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"id": "ui_settings_plugin",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["ui_settings_plugin"],
|
||||
"server": true,
|
||||
"ui": true
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "ui_settings_plugin",
|
||||
"version": "1.0.0",
|
||||
"main": "target/test/plugin_functional/plugins/ui_settings_plugin",
|
||||
"kibana": {
|
||||
"version": "kibana",
|
||||
"templateVersion": "1.0.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"kbn": "node ../../../../scripts/kbn.js",
|
||||
"build": "rm -rf './target' && tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "3.5.3"
|
||||
}
|
||||
}
|
|
@ -16,8 +16,6 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { UiSettingsPlugin } from './plugin';
|
||||
|
||||
export { deleteRoute } from './delete';
|
||||
export { getRoute } from './get';
|
||||
export { setManyRoute } from './set_many';
|
||||
export { setRoute } from './set';
|
||||
export const plugin = () => new UiSettingsPlugin();
|
|
@ -16,22 +16,22 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Legacy } from 'kibana';
|
||||
|
||||
async function handleRequest(request: Legacy.Request) {
|
||||
const { key } = request.params;
|
||||
const uiSettings = request.getUiSettingsService();
|
||||
import { CoreSetup, Plugin } from 'kibana/public';
|
||||
|
||||
await uiSettings.remove(key);
|
||||
return {
|
||||
settings: await uiSettings.getUserProvided(),
|
||||
};
|
||||
declare global {
|
||||
interface Window {
|
||||
uiSettingsPlugin?: Record<string, any>;
|
||||
uiSettingsPluginValue?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteRoute = {
|
||||
path: '/api/kibana/settings/{key}',
|
||||
method: 'DELETE',
|
||||
handler: async (request: Legacy.Request) => {
|
||||
return await handleRequest(request);
|
||||
},
|
||||
};
|
||||
export class UiSettingsPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
window.uiSettingsPlugin = core.uiSettings.getAll().ui_settings_plugin;
|
||||
window.uiSettingsPluginValue = core.uiSettings.get('ui_settings_plugin');
|
||||
}
|
||||
|
||||
public start() {}
|
||||
public stop() {}
|
||||
}
|
|
@ -17,8 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { KibanaSupertestProvider } from './supertest';
|
||||
import { UiSettingsPlugin } from './plugin';
|
||||
|
||||
export const services = {
|
||||
supertest: KibanaSupertestProvider,
|
||||
};
|
||||
export const plugin = () => new UiSettingsPlugin();
|
|
@ -16,35 +16,29 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Legacy } from 'kibana';
|
||||
import Joi from 'joi';
|
||||
|
||||
async function handleRequest(request: Legacy.Request) {
|
||||
const { changes } = request.payload as any;
|
||||
const uiSettings = request.getUiSettingsService();
|
||||
import { Plugin, CoreSetup } from 'kibana/server';
|
||||
|
||||
await uiSettings.setMany(changes);
|
||||
export class UiSettingsPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
core.uiSettings.register({
|
||||
ui_settings_plugin: {
|
||||
name: 'from_ui_settings_plugin',
|
||||
description: 'just for testing',
|
||||
value: '2',
|
||||
category: ['any'],
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
settings: await uiSettings.getUserProvided(),
|
||||
};
|
||||
const router = core.http.createRouter();
|
||||
router.get({ path: '/api/ui-settings-plugin', validate: false }, async (context, req, res) => {
|
||||
const uiSettingsValue = await context.core.uiSettings.client.get<number>(
|
||||
'ui_settings_plugin'
|
||||
);
|
||||
return res.ok({ body: { uiSettingsValue } });
|
||||
});
|
||||
}
|
||||
|
||||
public start() {}
|
||||
public stop() {}
|
||||
}
|
||||
|
||||
export const setManyRoute = {
|
||||
path: '/api/kibana/settings',
|
||||
method: 'POST',
|
||||
config: {
|
||||
validate: {
|
||||
payload: Joi.object()
|
||||
.keys({
|
||||
changes: Joi.object()
|
||||
.unknown(true)
|
||||
.required(),
|
||||
})
|
||||
.required(),
|
||||
},
|
||||
handler(request: Legacy.Request) {
|
||||
return handleRequest(request);
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"server/**/*.ts",
|
||||
"../../../../typings/**/*",
|
||||
],
|
||||
"exclude": []
|
||||
}
|
|
@ -16,19 +16,14 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Legacy } from 'kibana';
|
||||
import { GenericFtrProviderContext } from '@kbn/test/types/ftr';
|
||||
import { FtrProviderContext } from 'test/functional/ftr_provider_context';
|
||||
|
||||
async function handleRequest(request: Legacy.Request) {
|
||||
const uiSettings = request.getUiSettingsService();
|
||||
return {
|
||||
settings: await uiSettings.getUserProvided(),
|
||||
};
|
||||
}
|
||||
import { KibanaSupertestProvider } from './supertest';
|
||||
|
||||
export const getRoute = {
|
||||
path: '/api/kibana/settings',
|
||||
method: 'GET',
|
||||
handler(request: Legacy.Request) {
|
||||
return handleRequest(request);
|
||||
},
|
||||
export const services = {
|
||||
supertest: KibanaSupertestProvider,
|
||||
};
|
||||
|
||||
export type PluginFunctionalProviderContext = FtrProviderContext &
|
||||
GenericFtrProviderContext<typeof services, {}>;
|
|
@ -16,13 +16,12 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
import { format as formatUrl } from 'url';
|
||||
import { FtrProviderContext } from 'test/functional/ftr_provider_context';
|
||||
|
||||
import supertestAsPromised from 'supertest-as-promised';
|
||||
|
||||
export function KibanaSupertestProvider({ getService }) {
|
||||
export function KibanaSupertestProvider({ getService }: FtrProviderContext) {
|
||||
const config = getService('config');
|
||||
const kibanaServerUrl = formatUrl(config.get('servers.kibana'));
|
||||
return supertestAsPromised(kibanaServerUrl);
|
|
@ -18,8 +18,10 @@
|
|||
*/
|
||||
import url from 'url';
|
||||
import expect from '@kbn/expect';
|
||||
import { PluginFunctionalProviderContext } from '../../services';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) {
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
|
||||
const browser = getService('browser');
|
||||
|
@ -29,16 +31,16 @@ export default function ({ getService, getPageObjects }) {
|
|||
const loadingScreenNotShown = async () =>
|
||||
expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false);
|
||||
|
||||
const loadingScreenShown = () =>
|
||||
testSubjects.existOrFail('kbnLoadingMessage');
|
||||
const loadingScreenShown = () => testSubjects.existOrFail('kbnLoadingMessage');
|
||||
|
||||
const getKibanaUrl = (pathname, search) => url.format({
|
||||
protocol: 'http:',
|
||||
hostname: process.env.TEST_KIBANA_HOST || 'localhost',
|
||||
port: process.env.TEST_KIBANA_PORT || '5620',
|
||||
pathname,
|
||||
search,
|
||||
});
|
||||
const getKibanaUrl = (pathname?: string, search?: string) =>
|
||||
url.format({
|
||||
protocol: 'http:',
|
||||
hostname: process.env.TEST_KIBANA_HOST || 'localhost',
|
||||
port: process.env.TEST_KIBANA_PORT || '5620',
|
||||
pathname,
|
||||
search,
|
||||
});
|
||||
|
||||
describe('ui applications', function describeIndexTests() {
|
||||
before(async () => {
|
|
@ -16,13 +16,16 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { PluginFunctionalProviderContext } from '../../services';
|
||||
|
||||
export default function ({ loadTestFile }) {
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ loadTestFile }: PluginFunctionalProviderContext) {
|
||||
describe('core plugins', () => {
|
||||
loadTestFile(require.resolve('./applications'));
|
||||
loadTestFile(require.resolve('./legacy_plugins'));
|
||||
loadTestFile(require.resolve('./server_plugins'));
|
||||
loadTestFile(require.resolve('./ui_plugins'));
|
||||
loadTestFile(require.resolve('./ui_settings'));
|
||||
loadTestFile(require.resolve('./top_nav'));
|
||||
});
|
||||
}
|
|
@ -18,13 +18,15 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { PluginFunctionalProviderContext } from '../../services';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) {
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('legacy plugins', function describeIndexTests() {
|
||||
describe('legacy plugins', () => {
|
||||
describe('http', () => {
|
||||
it('has access to New Platform HTTP service', async () => {
|
||||
await supertest
|
||||
|
@ -41,7 +43,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('application service compatibility layer', function describeIndexTests() {
|
||||
describe('application service compatibility layer', () => {
|
||||
it('can render legacy apps', async () => {
|
||||
await PageObjects.common.navigateToApp('core_plugin_legacy');
|
||||
expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true);
|
|
@ -16,20 +16,18 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { PluginFunctionalProviderContext } from '../../services';
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
const browser = getService('browser');
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ getService }: PluginFunctionalProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('server plugins', function describeIndexTests() {
|
||||
it('extend request handler context', async () => {
|
||||
const url = `${PageObjects.common.getHostPort()}/core_plugin_b/`;
|
||||
await browser.get(url);
|
||||
|
||||
const pageSource = await browser.execute('return window.document.body.textContent;');
|
||||
expect(pageSource).to.equal('Pong via plugin A: true');
|
||||
await supertest
|
||||
.get('/core_plugin_b')
|
||||
.expect(200)
|
||||
.expect('Pong via plugin A: true');
|
||||
});
|
||||
});
|
||||
}
|
|
@ -18,12 +18,14 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { PluginFunctionalProviderContext } from '../../services';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) {
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
const browser = getService('browser');
|
||||
|
||||
describe('ui plugins', function () {
|
||||
describe('ui plugins', function() {
|
||||
describe('loading', function describeIndexTests() {
|
||||
before(async () => {
|
||||
await PageObjects.common.navigateToApp('settings');
|
||||
|
@ -40,7 +42,9 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('should attach string to window.corePluginB', async () => {
|
||||
const hasAccessToInjectedMetadata = await browser.execute('return window.hasAccessToInjectedMetadata');
|
||||
const hasAccessToInjectedMetadata = await browser.execute(
|
||||
'return window.hasAccessToInjectedMetadata'
|
||||
);
|
||||
expect(hasAccessToInjectedMetadata).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
@ -50,7 +54,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('should attach pluginContext to window.corePluginB', async () => {
|
||||
const envData = await browser.execute('return window.env');
|
||||
const envData: any = await browser.execute('return window.env');
|
||||
expect(envData.mode.dev).to.be(true);
|
||||
expect(envData.packageInfo.version).to.be.a('string');
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { PluginFunctionalProviderContext } from '../../services';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) {
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
const browser = getService('browser');
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('ui settings', function() {
|
||||
before(async () => {
|
||||
await PageObjects.common.navigateToApp('settings');
|
||||
});
|
||||
|
||||
it('client plugins have access to registered settings', async () => {
|
||||
const settings = await browser.execute('return window.uiSettingsPlugin');
|
||||
expect(settings).to.eql({
|
||||
category: ['any'],
|
||||
description: 'just for testing',
|
||||
name: 'from_ui_settings_plugin',
|
||||
value: '2',
|
||||
});
|
||||
const settingsValue = await browser.execute('return window.uiSettingsPluginValue');
|
||||
expect(settingsValue).to.be('2');
|
||||
});
|
||||
|
||||
it('server plugins have access to registered settings', async () => {
|
||||
await supertest
|
||||
.get('/api/ui-settings-plugin')
|
||||
.expect(200)
|
||||
.expect({ uiSettingsValue: 2 });
|
||||
});
|
||||
});
|
||||
}
|
21
yarn.lock
21
yarn.lock
|
@ -3045,6 +3045,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.2.5.tgz#582b2476169a6cba460a214d476c744441d873d5"
|
||||
integrity sha1-WCskdhaabLpGCiFNR2x0REHYc9U=
|
||||
|
||||
"@types/bluebird@*":
|
||||
version "3.5.28"
|
||||
resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.28.tgz#04c1a520ff076649236bc8ca21198542ce2bdb09"
|
||||
integrity sha512-0Vk/kqkukxPKSzP9c8WJgisgGDx5oZDbsLLWIP5t70yThO/YleE+GEm2S1GlRALTaack3O7U5OS5qEm7q2kciA==
|
||||
|
||||
"@types/bluebird@^3.1.1":
|
||||
version "3.5.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.20.tgz#f6363172add6f4eabb8cada53ca9af2781e8d6a1"
|
||||
|
@ -3986,6 +3991,22 @@
|
|||
"@types/cookiejar" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/supertest-as-promised@^2.0.38":
|
||||
version "2.0.38"
|
||||
resolved "https://registry.yarnpkg.com/@types/supertest-as-promised/-/supertest-as-promised-2.0.38.tgz#5077adf2a31429e06ba8de6799ebdb796ad50fc7"
|
||||
integrity sha512-2vdlnsZBIgaX0DFNOACK4xFDqvoA1sAR78QD3LiDWGmzSfrRCNt1WFyUYe2Vf0QS03tZf6XC8bNlLaLYXhZbGA==
|
||||
dependencies:
|
||||
"@types/bluebird" "*"
|
||||
"@types/superagent" "*"
|
||||
"@types/supertest" "*"
|
||||
|
||||
"@types/supertest@*":
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.8.tgz#23801236e2b85204ed771a8e7c40febba7da2bda"
|
||||
integrity sha512-wcax7/ip4XSSJRLbNzEIUVy2xjcBIZZAuSd2vtltQfRK7kxhx5WMHbLHkYdxN3wuQCrwpYrg86/9byDjPXoGMA==
|
||||
dependencies:
|
||||
"@types/superagent" "*"
|
||||
|
||||
"@types/supertest@^2.0.5":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.5.tgz#18d082a667eaed22759be98f4923e0061ae70c62"
|
||||
|
|
Loading…
Reference in a new issue