diff --git a/.eslintrc.js b/.eslintrc.js index 56950a70970d4..12bdd11fc8528 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -150,6 +150,9 @@ module.exports = { '!src/core/server/index.ts', '!src/core/server/mocks.ts', '!src/core/server/types.ts', + // for absolute imports until fixed in + // https://github.com/elastic/kibana/issues/36096 + '!src/core/server/types', '!src/core/server/*.test.mocks.ts', 'src/plugins/**/public/**/*', diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md new file mode 100644 index 0000000000000..0ec3ac45c6cb5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [get](./kibana-plugin-server.iuisettingsclient.get.md) + +## IUiSettingsClient.get property + +Retrieves uiSettings values set by the user with fallbacks to default values if not specified. + +Signature: + +```typescript +get: (key: string) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md new file mode 100644 index 0000000000000..d6765a5e5407e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getAll](./kibana-plugin-server.iuisettingsclient.getall.md) + +## IUiSettingsClient.getAll property + +Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. + +Signature: + +```typescript +getAll: () => Promise>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md new file mode 100644 index 0000000000000..29faa6d945b43 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md @@ -0,0 +1,13 @@ + + +[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) + +Signature: + +```typescript +getDefaults: () => Record; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md new file mode 100644 index 0000000000000..9a449b64ed5d0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) + +## IUiSettingsClient.getUserProvided property + +Retrieves a set of all uiSettings values set by the user. + +Signature: + +```typescript +getUserProvided: () => Promise>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.isoverridden.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.isoverridden.md new file mode 100644 index 0000000000000..a53655763a79b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.isoverridden.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [isOverridden](./kibana-plugin-server.iuisettingsclient.isoverridden.md) + +## IUiSettingsClient.isOverridden property + +Shows whether the uiSettings value set by the user. + +Signature: + +```typescript +isOverridden: (key: string) => boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md new file mode 100644 index 0000000000000..142f33d27c385 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) + +## IUiSettingsClient interface + +Service that provides access to the UiSettings stored in elasticsearch. + +Signature: + +```typescript +export interface IUiSettingsClient +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [get](./kibana-plugin-server.iuisettingsclient.get.md) | <T extends SavedObjectAttribute = any>(key: string) => Promise<T> | Retrieves uiSettings values set by the user with fallbacks to default values if not specified. | +| [getAll](./kibana-plugin-server.iuisettingsclient.getall.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, T>> | 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) | () => Record<string, UiSettingsParams> | Returns uiSettings default values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | +| [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, {
userValue?: T;
isOverridden?: boolean;
}>> | Retrieves a set of all uiSettings values set by the user. | +| [isOverridden](./kibana-plugin-server.iuisettingsclient.isoverridden.md) | (key: string) => boolean | Shows whether the uiSettings value set by the user. | +| [remove](./kibana-plugin-server.iuisettingsclient.remove.md) | (key: string) => Promise<void> | Removes uiSettings value by key. | +| [removeMany](./kibana-plugin-server.iuisettingsclient.removemany.md) | (keys: string[]) => Promise<void> | Removes multiple uiSettings values by keys. | +| [set](./kibana-plugin-server.iuisettingsclient.set.md) | <T extends SavedObjectAttribute = any>(key: string, value: T) => Promise<void> | Writes uiSettings value and marks it as set by the user. | +| [setMany](./kibana-plugin-server.iuisettingsclient.setmany.md) | <T extends SavedObjectAttribute = any>(changes: Record<string, T>) => Promise<void> | Writes multiple uiSettings values and marks them as set by the user. | + diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.remove.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.remove.md new file mode 100644 index 0000000000000..fa15b11fd76e4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.remove.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [remove](./kibana-plugin-server.iuisettingsclient.remove.md) + +## IUiSettingsClient.remove property + +Removes uiSettings value by key. + +Signature: + +```typescript +remove: (key: string) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.removemany.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.removemany.md new file mode 100644 index 0000000000000..ef5f994707aae --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.removemany.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [removeMany](./kibana-plugin-server.iuisettingsclient.removemany.md) + +## IUiSettingsClient.removeMany property + +Removes multiple uiSettings values by keys. + +Signature: + +```typescript +removeMany: (keys: string[]) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md new file mode 100644 index 0000000000000..bc67d05b3f0ee --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [set](./kibana-plugin-server.iuisettingsclient.set.md) + +## IUiSettingsClient.set property + +Writes uiSettings value and marks it as set by the user. + +Signature: + +```typescript +set: (key: string, value: T) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md new file mode 100644 index 0000000000000..ec2c24951f0ec --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [setMany](./kibana-plugin-server.iuisettingsclient.setmany.md) + +## IUiSettingsClient.setMany property + +Writes multiple uiSettings values and marks them as set by the user. + +Signature: + +```typescript +setMany: (changes: Record) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index c085d4cdf0d42..72464670b0b0e 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -63,6 +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. | | [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) | | @@ -111,6 +112,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | | [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. | ## Variables @@ -162,4 +164,5 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | +| [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md new file mode 100644 index 0000000000000..47aedbfbf2810 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [category](./kibana-plugin-server.uisettingsparams.category.md) + +## UiSettingsParams.category property + +used to group the configured setting in the UI + +Signature: + +```typescript +category: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md new file mode 100644 index 0000000000000..8d8887285ae2e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [description](./kibana-plugin-server.uisettingsparams.description.md) + +## UiSettingsParams.description property + +description provided to a user in UI + +Signature: + +```typescript +description: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md new file mode 100644 index 0000000000000..275111c05eff9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) + +## UiSettingsParams interface + +UiSettings parameters defined by the plugins. + +Signature: + +```typescript +export interface UiSettingsParams +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [category](./kibana-plugin-server.uisettingsparams.category.md) | string[] | used to group the configured setting in the UI | +| [description](./kibana-plugin-server.uisettingsparams.description.md) | string | description provided to a user in UI | +| [name](./kibana-plugin-server.uisettingsparams.name.md) | string | title in the UI | +| [optionLabels](./kibana-plugin-server.uisettingsparams.optionlabels.md) | Record<string, string> | text labels for 'select' type UI element | +| [options](./kibana-plugin-server.uisettingsparams.options.md) | string[] | a range of valid values | +| [readonly](./kibana-plugin-server.uisettingsparams.readonly.md) | boolean | a flag indicating that value cannot be changed | +| [requiresPageReload](./kibana-plugin-server.uisettingsparams.requirespagereload.md) | boolean | a flag indicating whether new value applying requires page reloading | +| [type](./kibana-plugin-server.uisettingsparams.type.md) | UiSettingsType | defines a type of UI element [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | +| [value](./kibana-plugin-server.uisettingsparams.value.md) | SavedObjectAttribute | default value to fall back to if a user doesn't provide any | + diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md new file mode 100644 index 0000000000000..2b414eefffed2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [name](./kibana-plugin-server.uisettingsparams.name.md) + +## UiSettingsParams.name property + +title in the UI + +Signature: + +```typescript +name: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.optionlabels.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.optionlabels.md new file mode 100644 index 0000000000000..cb0e196fdcacc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.optionlabels.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [optionLabels](./kibana-plugin-server.uisettingsparams.optionlabels.md) + +## UiSettingsParams.optionLabels property + +text labels for 'select' type UI element + +Signature: + +```typescript +optionLabels?: Record; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md new file mode 100644 index 0000000000000..71eecdfabc4a0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [options](./kibana-plugin-server.uisettingsparams.options.md) + +## UiSettingsParams.options property + +a range of valid values + +Signature: + +```typescript +options?: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.readonly.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.readonly.md new file mode 100644 index 0000000000000..faec4d6eadbcc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.readonly.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [readonly](./kibana-plugin-server.uisettingsparams.readonly.md) + +## UiSettingsParams.readonly property + +a flag indicating that value cannot be changed + +Signature: + +```typescript +readonly?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.requirespagereload.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.requirespagereload.md new file mode 100644 index 0000000000000..224b3695224b9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.requirespagereload.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [requiresPageReload](./kibana-plugin-server.uisettingsparams.requirespagereload.md) + +## UiSettingsParams.requiresPageReload property + +a flag indicating whether new value applying requires page reloading + +Signature: + +```typescript +requiresPageReload?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.type.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.type.md new file mode 100644 index 0000000000000..ccf2d67b2dffb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.type.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [type](./kibana-plugin-server.uisettingsparams.type.md) + +## UiSettingsParams.type property + +defines a type of UI element [UiSettingsType](./kibana-plugin-server.uisettingstype.md) + +Signature: + +```typescript +type?: UiSettingsType; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md new file mode 100644 index 0000000000000..455756899ecfc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [value](./kibana-plugin-server.uisettingsparams.value.md) + +## UiSettingsParams.value property + +default value to fall back to if a user doesn't provide any + +Signature: + +```typescript +value: SavedObjectAttribute; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingstype.md b/docs/development/core/server/kibana-plugin-server.uisettingstype.md new file mode 100644 index 0000000000000..789d4d5788468 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingstype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsType](./kibana-plugin-server.uisettingstype.md) + +## UiSettingsType type + +UI element type to represent the settings. + +Signature: + +```typescript +export declare type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; +``` diff --git a/src/core/server/core_context.mock.ts b/src/core/server/core_context.mock.ts index e8c0a0a4830bf..d287348e19079 100644 --- a/src/core/server/core_context.mock.ts +++ b/src/core/server/core_context.mock.ts @@ -32,7 +32,7 @@ function create({ env?: Env; logger?: jest.Mocked; configService?: jest.Mocked; -} = {}): CoreContext { +} = {}): DeeplyMockedKeys { return { coreId: Symbol(), env, logger, configService }; } diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index a42d38fd4cb70..c4a61aaf83ac7 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -38,6 +38,7 @@ export const config = { validate: match(validBasePathRegex, "must start with a slash, don't end with one"), }) ), + defaultRoute: schema.maybe(schema.string()), cors: schema.conditional( schema.contextRef('dev'), true, @@ -106,6 +107,7 @@ export class HttpConfig { public basePath?: string; public rewriteBasePath: boolean; public publicDir: string; + public defaultRoute?: string; public ssl: SslConfig; /** @@ -123,5 +125,6 @@ export class HttpConfig { this.rewriteBasePath = rawConfig.rewriteBasePath; this.publicDir = env.staticFilesDir; this.ssl = new SslConfig(rawConfig.ssl || {}); + this.defaultRoute = rawConfig.defaultRoute; } } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 40a140f154a6e..00c9aedc42cfb 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -68,6 +68,7 @@ const createSetupContractMock = () => { getAuthHeaders: jest.fn(), }, isTlsEnabled: false, + config: {}, }; setupContract.createCookieSessionStorageFactory.mockResolvedValue( sessionStorageMock.createFactory() diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index c720d2fea4fc6..caebd768c70e5 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -106,6 +106,10 @@ export class HttpService implements CoreService ) => this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName, provider), + + config: { + defaultRoute: config.defaultRoute, + }, }; return contract; diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index d75028ca12d66..6f5cb02fd8cba 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -231,6 +231,16 @@ export interface InternalHttpServiceSetup contextName: T, provider: RequestHandlerContextProvider ) => RequestHandlerContextContainer; + config: { + /** + * @internalRemarks + * Deprecated part of the server config, provided until + * https://github.com/elastic/kibana/issues/40255 + * + * @deprecated + * */ + defaultRoute?: string; + }; } /** @public */ diff --git a/src/core/server/index.ts b/src/core/server/index.ts index d92c92841bb48..7ea4aa304c4d6 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -48,6 +48,8 @@ import { InternalHttpServiceSetup, HttpServiceSetup } from './http'; import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins'; import { ContextSetup } from './context'; import { SavedObjectsServiceStart } from './saved_objects'; + +import { InternalUiSettingsServiceSetup } from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; export { bootstrap } from './bootstrap'; @@ -163,6 +165,13 @@ export { SavedObjectsUpdateResponse, } from './saved_objects'; +export { + IUiSettingsClient, + UiSettingsParams, + InternalUiSettingsServiceSetup, + UiSettingsType, +} from './ui_settings'; + export { RecursiveReadonly } from '../utils'; export { @@ -230,6 +239,7 @@ export interface InternalCoreSetup { context: ContextSetup; http: InternalHttpServiceSetup; elasticsearch: InternalElasticsearchServiceSetup; + uiSettings: InternalUiSettingsServiceSetup; } /** diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index bdb1499e065ca..590bd192e3ded 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -30,52 +30,33 @@ jest.mock('./plugins/find_legacy_plugin_specs.ts', () => ({ }), })); -import { LegacyService } from '.'; +import { LegacyService, LegacyServiceSetupDeps, LegacyServiceStartDeps } from '.'; // @ts-ignore: implicit any for JS file import MockClusterManager from '../../../cli/cluster/cluster_manager'; import KbnServer from '../../../legacy/server/kbn_server'; import { Config, Env, ObjectToConfigAdapter } from '../config'; -import { ContextSetup } from '../context'; import { contextServiceMock } from '../context/context_service.mock'; import { getEnvOptions } from '../config/__mocks__/env'; import { configServiceMock } from '../config/config_service.mock'; -import { InternalElasticsearchServiceSetup } from '../elasticsearch'; -import { HttpServiceStart, BasePathProxyServer } from '../http'; + +import { BasePathProxyServer } from '../http'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { DiscoveredPlugin, DiscoveredPluginInternal } from '../plugins'; -import { PluginsServiceSetup, PluginsServiceStart } from '../plugins/plugins_service'; -import { - SavedObjectsServiceStart, - SavedObjectsServiceSetup, -} from 'src/core/server/saved_objects/saved_objects_service'; + import { KibanaMigrator } from '../saved_objects/migrations'; import { ISavedObjectsClientProvider } from '../saved_objects'; import { httpServiceMock } from '../http/http_service.mock'; +import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; let coreId: symbol; let env: Env; let config$: BehaviorSubject; -let setupDeps: { - core: { - context: ContextSetup; - elasticsearch: InternalElasticsearchServiceSetup; - http: any; - plugins: PluginsServiceSetup; - savedObjects: SavedObjectsServiceSetup; - }; - plugins: Record; -}; - -let startDeps: { - core: { - http: HttpServiceStart; - savedObjects: SavedObjectsServiceStart; - plugins: PluginsServiceStart; - }; - plugins: Record; -}; + +let setupDeps: LegacyServiceSetupDeps; + +let startDeps: LegacyServiceStartDeps; const logger = loggingServiceMock.create(); let configService: ReturnType; @@ -91,12 +72,14 @@ beforeEach(() => { core: { context: contextServiceMock.createSetupContract(), elasticsearch: { legacy: {} } as any, + uiSettings: uiSettingsServiceMock.createSetup(), http: { ...httpServiceMock.createSetupContract(), auth: { getAuthHeaders: () => undefined, - }, + } as any, }, + plugins: { contracts: new Map([['plugin-id', 'plugin-value']]), uiPlugins: { @@ -104,18 +87,12 @@ beforeEach(() => { internal: new Map([['plugin-id', {} as DiscoveredPluginInternal]]), }, }, - savedObjects: { - clientProvider: {} as ISavedObjectsClientProvider, - }, }, plugins: { 'plugin-id': 'plugin-value' }, }; startDeps = { core: { - http: { - isListening: () => true, - }, savedObjects: { migrator: {} as KibanaMigrator, clientProvider: {} as ISavedObjectsClientProvider, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index edd913fe16a63..dd3ee3db89153 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -275,6 +275,7 @@ export class LegacyService implements CoreService { kibanaMigrator: startDeps.core.savedObjects.migrator, uiPlugins: setupDeps.core.plugins.uiPlugins, elasticsearch: setupDeps.core.elasticsearch, + uiSettings: setupDeps.core.uiSettings, savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider, }, logger: this.coreContext.logger, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index c3d524c77402c..fb703c6c35008 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -30,6 +30,7 @@ export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service. export { httpServiceMock } from './http/http_service.mock'; export { loggingServiceMock } from './logging/logging_service.mock'; export { SavedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock'; +export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export function pluginInitializerContextConfigMock(config: T) { const mock: jest.Mocked['config']> = { diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 46e5d2b6ab6c6..674d2c1e7bf5e 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -722,6 +722,8 @@ export interface InternalCoreSetup { // // (undocumented) http: InternalHttpServiceSetup; + // (undocumented) + uiSettings: InternalUiSettingsServiceSetup; } // @internal (undocumented) @@ -732,6 +734,12 @@ export interface InternalCoreStart { savedObjects: SavedObjectsServiceStart; } +// @internal (undocumented) +export interface InternalUiSettingsServiceSetup { + asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; + setDefaults(values: Record): void; +} + // @public export interface IRouter { delete:

(route: RouteConfig, handler: RequestHandler) => void; @@ -751,6 +759,22 @@ export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolea // @public export type IScopedClusterClient = Pick; +// @public +export interface IUiSettingsClient { + get: (key: string) => Promise; + getAll: () => Promise>; + getDefaults: () => Record; + getUserProvided: () => Promise>; + isOverridden: (key: string) => boolean; + remove: (key: string) => Promise; + removeMany: (keys: string[]) => Promise; + set: (key: string, value: T) => Promise; + setMany: (changes: Record) => Promise; +} + // @public export class KibanaRequest { // @internal (undocumented) @@ -1545,6 +1569,22 @@ export interface SessionStorageFactory { asScoped: (request: KibanaRequest) => SessionStorage; } +// @public +export interface UiSettingsParams { + category: string[]; + description: string; + name: string; + optionLabels?: Record; + options?: string[]; + readonly?: boolean; + requiresPageReload?: boolean; + type?: UiSettingsType; + value: SavedObjectAttribute; +} + +// @public +export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; + // Warnings were encountered during analysis: // diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 455bb97d47b57..3e4f5c2e5a813 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -25,6 +25,7 @@ import { ElasticsearchService, ElasticsearchServiceSetup } from './elasticsearch import { HttpService, InternalHttpServiceSetup } from './http'; import { LegacyService } from './legacy'; import { Logger, LoggerFactory } from './logging'; +import { UiSettingsService } from './ui_settings'; import { PluginsService, config as pluginsConfig } from './plugins'; import { SavedObjectsService } from '../server/saved_objects'; @@ -34,6 +35,7 @@ import { config as loggingConfig } from './logging'; import { config as devConfig } from './dev'; import { config as kibanaConfig } from './kibana_config'; import { config as savedObjectsConfig } from './saved_objects'; +import { config as uiSettingsConfig } from './ui_settings'; import { mapToObject } from '../utils/'; import { ContextService } from './context'; import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service'; @@ -50,6 +52,7 @@ export class Server { private readonly log: Logger; private readonly plugins: PluginsService; private readonly savedObjects: SavedObjectsService; + private readonly uiSettings: UiSettingsService; constructor( readonly config$: Observable, @@ -66,6 +69,7 @@ export class Server { this.legacy = new LegacyService(core); this.elasticsearch = new ElasticsearchService(core); this.savedObjects = new SavedObjectsService(core); + this.uiSettings = new UiSettingsService(core); } public async setup() { @@ -89,10 +93,15 @@ export class Server { http: httpSetup, }); + const uiSettingsSetup = await this.uiSettings.setup({ + http: httpSetup, + }); + const coreSetup = { context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, + uiSettings: uiSettingsSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); @@ -184,6 +193,7 @@ export class Server { [devConfig.path, devConfig.schema], [kibanaConfig.path, kibanaConfig.schema], [savedObjectsConfig.path, savedObjectsConfig.schema], + [uiSettingsConfig.path, uiSettingsConfig.schema], ]; for (const [path, schema] of schemas) { diff --git a/src/legacy/ui/ui_settings/create_objects_client_stub.ts b/src/core/server/ui_settings/create_objects_client_stub.ts similarity index 96% rename from src/legacy/ui/ui_settings/create_objects_client_stub.ts rename to src/core/server/ui_settings/create_objects_client_stub.ts index ad19b5c8bc7cf..d52ec58fa7e37 100644 --- a/src/legacy/ui/ui_settings/create_objects_client_stub.ts +++ b/src/core/server/ui_settings/create_objects_client_stub.ts @@ -18,7 +18,7 @@ */ import sinon from 'sinon'; -import { SavedObjectsClient } from '../../../../src/core/server'; +import { SavedObjectsClient } from '../saved_objects'; export const savedObjectsClientErrors = SavedObjectsClient.errors; diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts similarity index 87% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts rename to src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index 654c0fbb66c8b..5f7e915365873 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -18,14 +18,13 @@ */ import sinon from 'sinon'; -import expect from '@kbn/expect'; import Chance from 'chance'; +import { loggingServiceMock } from '../../logging/logging_service.mock'; import * as getUpgradeableConfigNS from './get_upgradeable_config'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; const chance = new Chance(); - describe('uiSettings/createOrUpgradeSavedConfig', function() { const sandbox = sinon.createSandbox(); afterEach(() => sandbox.restore()); @@ -35,7 +34,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { const buildNum = chance.integer({ min: 1000, max: 5000 }); function setup() { - const logWithMetadata = sinon.stub(); + const logger = loggingServiceMock.create(); const getUpgradeableConfig = sandbox.stub(getUpgradeableConfigNS, 'getUpgradeableConfig'); const savedObjectsClient = { create: sinon.stub().callsFake(async (type, attributes, options = {}) => ({ @@ -50,7 +49,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { savedObjectsClient, version, buildNum, - logWithMetadata, + log: logger.get(), ...options, }); @@ -62,7 +61,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { return { buildNum, - logWithMetadata, + logger, run, version, savedObjectsClient, @@ -126,7 +125,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); it('should log a message for upgrades', async () => { - const { getUpgradeableConfig, logWithMetadata, run } = setup(); + const { getUpgradeableConfig, logger, run } = setup(); getUpgradeableConfig.resolves({ id: prevVersion, @@ -136,20 +135,21 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); await run(); - sinon.assert.calledOnce(logWithMetadata); - sinon.assert.calledWithExactly( - logWithMetadata, - ['plugin', 'elasticsearch'], - sinon.match('Upgrade'), - sinon.match({ - prevVersion, - newVersion: version, - }) - ); + expect(loggingServiceMock.collect(logger).debug).toMatchInlineSnapshot(` + Array [ + Array [ + "Upgrade config from 4.0.0 to 4.0.1", + Object { + "newVersion": "4.0.1", + "prevVersion": "4.0.0", + }, + ], + ] + `); }); it('does not log when upgrade fails', async () => { - const { getUpgradeableConfig, logWithMetadata, run, savedObjectsClient } = setup(); + const { getUpgradeableConfig, logger, run, savedObjectsClient } = setup(); getUpgradeableConfig.resolves({ id: prevVersion, @@ -166,10 +166,10 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { await run(); throw new Error('Expected run() to throw an error'); } catch (error) { - expect(error.message).to.be('foo'); + expect(error.message).toBe('foo'); } - sinon.assert.notCalled(logWithMetadata); + expect(loggingServiceMock.collect(logger).debug).toHaveLength(0); }); }); @@ -198,7 +198,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); const result = await run({ onWriteError: () => 123 }); - expect(result).to.be(123); + expect(result).toBe(123); }); it('rejects with the error from onWriteError() if it rejects', async () => { @@ -214,7 +214,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); throw new Error('expected run() to reject'); } catch (error) { - expect(error.message).to.be('foo bar'); + expect(error.message).toBe('foo bar'); } }); @@ -233,7 +233,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); throw new Error('expected run() to reject'); } catch (error) { - expect(error.message).to.be('foo bar'); + expect(error.message).toBe('foo bar'); } }); @@ -250,7 +250,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); throw new Error('expected run() to reject'); } catch (error) { - expect(error.message).to.be('foo'); + expect(error.message).toBe('foo'); } }); }); diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts similarity index 82% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts rename to src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts index 0dc3d5f50e97e..1655297adb6c9 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts @@ -18,8 +18,9 @@ */ import { defaults } from 'lodash'; -import { SavedObjectsClientContract, SavedObjectAttribute } from 'src/core/server'; -import { Legacy } from 'kibana'; + +import { SavedObjectsClientContract, SavedObjectAttribute } from '../../saved_objects/types'; +import { Logger } from '../../logging'; import { getUpgradeableConfig } from './get_upgradeable_config'; @@ -27,7 +28,7 @@ interface Options { savedObjectsClient: SavedObjectsClientContract; version: string; buildNum: number; - logWithMetadata: Legacy.Server['logWithMetadata']; + log: Logger; onWriteError?: ( error: Error, attributes: Record @@ -36,7 +37,7 @@ interface Options { export async function createOrUpgradeSavedConfig( options: Options ): Promise | undefined> { - const { savedObjectsClient, version, buildNum, logWithMetadata, onWriteError } = options; + const { savedObjectsClient, version, buildNum, log, onWriteError } = options; // try to find an older config we can upgrade const upgradeableConfig = await getUpgradeableConfig({ @@ -59,13 +60,9 @@ export async function createOrUpgradeSavedConfig { let savedObjectsClient: SavedObjectsClientContract; let kbnServer: KbnServer; @@ -88,7 +89,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '5.4.0', buildNum: 54099, - logWithMetadata: sinon.stub(), + log: logger, }); const config540 = await savedObjectsClient.get('config', '5.4.0'); @@ -114,7 +115,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '5.4.1', buildNum: 54199, - logWithMetadata: sinon.stub(), + log: logger, }); const config541 = await savedObjectsClient.get('config', '5.4.1'); @@ -140,7 +141,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '7.0.0-rc1', buildNum: 70010, - logWithMetadata: sinon.stub(), + log: logger, }); const config700rc1 = await savedObjectsClient.get('config', '7.0.0-rc1'); @@ -167,7 +168,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '7.0.0', buildNum: 70099, - logWithMetadata: sinon.stub(), + log: logger, }); const config700 = await savedObjectsClient.get('config', '7.0.0'); @@ -195,7 +196,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '6.2.3-rc1', buildNum: 62310, - logWithMetadata: sinon.stub(), + log: logger, }); const config623rc1 = await savedObjectsClient.get('config', '6.2.3-rc1'); diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts similarity index 86% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts rename to src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts index 6bb2cb3b87850..073a6961fdec4 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts @@ -20,8 +20,6 @@ import expect from '@kbn/expect'; import { isConfigVersionUpgradeable } from './is_config_version_upgradeable'; -// @ts-ignore -import { pkg } from '../../../utils'; describe('savedObjects/health_check/isConfigVersionUpgradeable', function() { function isUpgradeableTest(savedVersion: string, kibanaVersion: string, expected: boolean) { @@ -30,10 +28,10 @@ describe('savedObjects/health_check/isConfigVersionUpgradeable', function() { }); } - isUpgradeableTest('1.0.0-beta1', pkg.version, false); - isUpgradeableTest('1.0.0-beta256', pkg.version, false); + isUpgradeableTest('1.0.0-beta1', '7.4.0', false); + isUpgradeableTest('1.0.0-beta256', '7.4.0', false); isUpgradeableTest('10.100.1000-beta256', '10.100.1000-beta257', false); - isUpgradeableTest(pkg.version, pkg.version, false); + isUpgradeableTest('7.4.0', '7.4.0', false); isUpgradeableTest('4.0.0-RC1', '4.0.0-RC2', true); isUpgradeableTest('10.100.1000-rc256', '10.100.1000-RC257', true); isUpgradeableTest('4.0.0-rc2', '4.0.0-rc1', false); @@ -48,6 +46,6 @@ describe('savedObjects/health_check/isConfigVersionUpgradeable', function() { isUpgradeableTest('4.1.0-rc1-SNAPSHOT', '4.1.0-rc1', false); isUpgradeableTest('5.0.0-alpha11', '5.0.0', false); isUpgradeableTest('50.0.10-rc150-SNAPSHOT', '50.0.9', false); - isUpgradeableTest(undefined as any, pkg.version, false); - isUpgradeableTest('@@version', pkg.version, false); + isUpgradeableTest(undefined as any, '7.4.0', false); + isUpgradeableTest('@@version', '7.4.0', false); }); diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts similarity index 100% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts rename to src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts diff --git a/src/core/server/ui_settings/index.ts b/src/core/server/ui_settings/index.ts new file mode 100644 index 0000000000000..edd0bfc4f3a89 --- /dev/null +++ b/src/core/server/ui_settings/index.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { + IUiSettingsClient, + UiSettingsClient, + UiSettingsServiceOptions, +} from './ui_settings_client'; + +export { config } from './ui_settings_config'; +export { + UiSettingsParams, + UiSettingsService, + InternalUiSettingsServiceSetup, + UiSettingsType, +} from './ui_settings_service'; diff --git a/src/legacy/ui/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts similarity index 94% rename from src/legacy/ui/ui_settings/ui_settings_service.test.ts rename to src/core/server/ui_settings/ui_settings_client.test.ts index f37076b27ad6f..59c13fbebee70 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -21,17 +21,20 @@ import expect from '@kbn/expect'; import Chance from 'chance'; import sinon from 'sinon'; -import { UiSettingsService } from './ui_settings_service'; +import { loggingServiceMock } from '../logging/logging_service.mock'; + +import { UiSettingsClient } from './ui_settings_client'; import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config'; import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub'; +const logger = loggingServiceMock.create().get(); + const TYPE = 'config'; const ID = 'kibana-version'; const BUILD_NUM = 1234; const chance = new Chance(); interface SetupOptions { - getDefaults?: () => Record; defaults?: Record; esDocSource?: Record; overrides?: Record; @@ -41,17 +44,18 @@ describe('ui settings', () => { const sandbox = sinon.createSandbox(); function setup(options: SetupOptions = {}) { - const { getDefaults, defaults = {}, overrides = {}, esDocSource = {} } = options; + const { defaults = {}, overrides = {}, esDocSource = {} } = options; const savedObjectsClient = createObjectsClientStub(esDocSource); - const uiSettings = new UiSettingsService({ + const uiSettings = new UiSettingsClient({ type: TYPE, id: ID, buildNum: BUILD_NUM, - getDefaults: getDefaults || (() => defaults), + defaults, savedObjectsClient, overrides, + log: logger, }); const createOrUpgradeSavedConfig = sandbox.stub( @@ -239,25 +243,10 @@ describe('ui settings', () => { }); describe('#getDefaults()', () => { - it('returns a promise for the defaults', async () => { - const { uiSettings } = setup(); - const promise = uiSettings.getDefaults(); - expect(promise).to.be.a(Promise); - expect(await promise).to.eql({}); - }); - }); - - describe('getDefaults() argument', () => { - it('casts sync `getDefaults()` to promise', () => { - const getDefaults = () => ({ key: { value: chance.word() } }); - const { uiSettings } = setup({ getDefaults }); - expect(uiSettings.getDefaults()).to.be.a(Promise); - }); - - it('returns the defaults returned by getDefaults() argument', async () => { + it('returns the defaults passed to the constructor', () => { const value = chance.word(); const { uiSettings } = setup({ defaults: { key: { value } } }); - expect(await uiSettings.getDefaults()).to.eql({ + expect(uiSettings.getDefaults()).to.eql({ key: { value }, }); }); diff --git a/src/legacy/ui/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_client.ts similarity index 63% rename from src/legacy/ui/ui_settings/ui_settings_service.ts rename to src/core/server/ui_settings/ui_settings_client.ts index 57312140b16b3..c495d1b4c4567 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { Legacy } from 'kibana'; import { defaultsDeep } from 'lodash'; import Boom from 'boom'; -import { SavedObjectsClientContract, SavedObjectAttribute } from 'src/core/server'; +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'; export interface UiSettingsServiceOptions { type: string; @@ -29,8 +30,8 @@ export interface UiSettingsServiceOptions { buildNum: number; savedObjectsClient: SavedObjectsClientContract; overrides?: Record; - getDefaults?: () => Record; - logWithMetadata?: Legacy.Server['logWithMetadata']; + defaults?: Record; + log: Logger; } interface ReadOptions { @@ -38,82 +39,87 @@ interface ReadOptions { autoCreateOrUpgradeIfMissing?: boolean; } -interface UserProvidedValue { - userValue?: SavedObjectAttribute; +interface UserProvidedValue { + userValue?: T; isOverridden?: boolean; } type UiSettingsRawValue = UiSettingsParams & UserProvidedValue; -type UserProvided = Record; +type UserProvided = Record>; type UiSettingsRaw = Record; -type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; - -interface UiSettingsParams { - name: string; - value: SavedObjectAttribute; - description: string; - category: string[]; - options?: string[]; - optionLabels?: Record; - requiresPageReload?: boolean; - readonly?: boolean; - type?: UiSettingsType; -} - +/** + * Service that provides access to the UiSettings stored in elasticsearch. + * + * @public + */ export interface IUiSettingsClient { - getDefaults: () => Promise>; + /** + * Returns uiSettings default values {@link UiSettingsParams} + */ + getDefaults: () => Record; + /** + * Retrieves uiSettings values set by the user with fallbacks to default values if not specified. + */ get: (key: string) => Promise; + /** + * Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. + */ getAll: () => Promise>; - getUserProvided: () => Promise; + /** + * Retrieves a set of all uiSettings values set by the user. + */ + getUserProvided: () => Promise< + Record + >; + /** + * Writes multiple uiSettings values and marks them as set by the user. + */ setMany: (changes: Record) => Promise; + /** + * Writes uiSettings value and marks it as set by the user. + */ set: (key: string, value: T) => Promise; + /** + * Removes uiSettings value by key. + */ remove: (key: string) => Promise; + /** + * Removes multiple uiSettings values by keys. + */ removeMany: (keys: string[]) => Promise; + /** + * Shows whether the uiSettings value set by the user. + */ isOverridden: (key: string) => boolean; } -/** - * Service that provides access to the UiSettings stored in elasticsearch. - * @class UiSettingsService - */ -export class UiSettingsService implements IUiSettingsClient { - private readonly _type: UiSettingsServiceOptions['type']; - private readonly _id: UiSettingsServiceOptions['id']; - private readonly _buildNum: UiSettingsServiceOptions['buildNum']; - private readonly _savedObjectsClient: UiSettingsServiceOptions['savedObjectsClient']; - private readonly _overrides: NonNullable; - private readonly _getDefaults: NonNullable; - private readonly _logWithMetadata: NonNullable; + +export class UiSettingsClient implements IUiSettingsClient { + private readonly type: UiSettingsServiceOptions['type']; + private readonly id: UiSettingsServiceOptions['id']; + private readonly buildNum: UiSettingsServiceOptions['buildNum']; + private readonly savedObjectsClient: UiSettingsServiceOptions['savedObjectsClient']; + private readonly overrides: NonNullable; + private readonly defaults: NonNullable; + private readonly log: Logger; constructor(options: UiSettingsServiceOptions) { - const { - type, - id, - buildNum, - savedObjectsClient, - // we use a function for getDefaults() so that defaults can be different in - // different scenarios, and so they can change over time - getDefaults = () => ({}), - // function that accepts log messages in the same format as server.logWithMetadata - logWithMetadata = () => {}, - overrides = {}, - } = options; - - this._type = type; - this._id = id; - this._buildNum = buildNum; - this._savedObjectsClient = savedObjectsClient; - this._getDefaults = getDefaults; - this._overrides = overrides; - this._logWithMetadata = logWithMetadata; + const { type, id, buildNum, savedObjectsClient, log, defaults = {}, overrides = {} } = options; + + this.type = type; + this.id = id; + this.buildNum = buildNum; + this.savedObjectsClient = savedObjectsClient; + this.defaults = defaults; + this.overrides = overrides; + this.log = log; } - async getDefaults() { - return await this._getDefaults(); + getDefaults() { + return this.defaults; } - // returns a Promise for the value of the requested setting async get(key: string): Promise { const all = await this.getAll(); return all[key]; @@ -135,14 +141,16 @@ export class UiSettingsService implements IUiSettingsClient { // NOTE: should be a private method async getRaw(): Promise { const userProvided = await this.getUserProvided(); - return defaultsDeep(userProvided, await this.getDefaults()); + return defaultsDeep(userProvided, this.defaults); } - async getUserProvided(options: ReadOptions = {}): Promise { + async getUserProvided( + options: ReadOptions = {} + ): Promise> { 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(options))) { if (userValue !== null && !this.isOverridden(key)) { userProvided[key] = { userValue, @@ -152,7 +160,7 @@ export class UiSettingsService implements IUiSettingsClient { // write all overridden keys, dropping the userValue is override is null and // adding keys for overrides that are not in saved object - for (const [key, userValue] of Object.entries(this._overrides)) { + for (const [key, userValue] of Object.entries(this.overrides)) { userProvided[key] = userValue === null ? { isOverridden: true } : { isOverridden: true, userValue }; } @@ -161,7 +169,7 @@ export class UiSettingsService implements IUiSettingsClient { } async setMany(changes: Record) { - await this._write({ changes }); + await this.write({ changes }); } async set(key: string, value: T) { @@ -181,7 +189,7 @@ export class UiSettingsService implements IUiSettingsClient { } isOverridden(key: string) { - return this._overrides.hasOwnProperty(key); + return this.overrides.hasOwnProperty(key); } // NOTE: should be private method @@ -191,7 +199,7 @@ export class UiSettingsService implements IUiSettingsClient { } } - private async _write({ + private async write({ changes, autoCreateOrUpgradeIfMissing = true, }: { @@ -203,28 +211,28 @@ export class UiSettingsService implements IUiSettingsClient { } try { - await this._savedObjectsClient.update(this._type, this._id, changes); + await this.savedObjectsClient.update(this.type, this.id, changes); } catch (error) { - const { isNotFoundError } = this._savedObjectsClient.errors; + const { isNotFoundError } = this.savedObjectsClient.errors; if (!isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) { throw error; } await createOrUpgradeSavedConfig({ - savedObjectsClient: this._savedObjectsClient, - version: this._id, - buildNum: this._buildNum, - logWithMetadata: this._logWithMetadata, + savedObjectsClient: this.savedObjectsClient, + version: this.id, + buildNum: this.buildNum, + log: this.log, }); - await this._write({ + await this.write({ changes, autoCreateOrUpgradeIfMissing: false, }); } } - private async _read({ + private async read({ ignore401Errors = false, autoCreateOrUpgradeIfMissing = true, }: ReadOptions = {}): Promise> { @@ -233,18 +241,18 @@ export class UiSettingsService implements IUiSettingsClient { isNotFoundError, isForbiddenError, isNotAuthorizedError, - } = this._savedObjectsClient.errors; + } = this.savedObjectsClient.errors; try { - const resp = await this._savedObjectsClient.get(this._type, this._id); + const resp = await this.savedObjectsClient.get(this.type, this.id); return resp.attributes; } catch (error) { if (isNotFoundError(error) && autoCreateOrUpgradeIfMissing) { const failedUpgradeAttributes = await createOrUpgradeSavedConfig({ - savedObjectsClient: this._savedObjectsClient, - version: this._id, - buildNum: this._buildNum, - logWithMetadata: this._logWithMetadata, + savedObjectsClient: this.savedObjectsClient, + version: this.id, + buildNum: this.buildNum, + log: this.log, onWriteError(writeError, attributes) { if (isConflictError(writeError)) { // trigger `!failedUpgradeAttributes` check below, since another @@ -262,7 +270,7 @@ export class UiSettingsService implements IUiSettingsClient { }); if (!failedUpgradeAttributes) { - return await this._read({ + return await this.read({ ignore401Errors, autoCreateOrUpgradeIfMissing: false, }); @@ -284,7 +292,7 @@ export class UiSettingsService implements IUiSettingsClient { isForbiddenError, isEsUnavailableError, isNotAuthorizedError, - } = this._savedObjectsClient.errors; + } = this.savedObjectsClient.errors; return ( isForbiddenError(error) || diff --git a/src/core/server/ui_settings/ui_settings_config.ts b/src/core/server/ui_settings/ui_settings_config.ts new file mode 100644 index 0000000000000..702286f953ef1 --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_config.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export type UiSettingsConfigType = TypeOf; + +export const config = { + path: 'uiSettings', + schema: schema.object({ + overrides: schema.object({}, { allowUnknowns: true }), + // Deprecation is implemented in LP. + // We define schema here not to fail on the validation step. + enabled: schema.maybe(schema.boolean()), + }), +}; diff --git a/src/legacy/ui/ui_settings/ui_settings_service.mock.ts b/src/core/server/ui_settings/ui_settings_service.mock.ts similarity index 71% rename from src/legacy/ui/ui_settings/ui_settings_service.mock.ts rename to src/core/server/ui_settings/ui_settings_service.mock.ts index 7c1a17ebd447c..2127faf0d2029 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service.mock.ts +++ b/src/core/server/ui_settings/ui_settings_service.mock.ts @@ -17,9 +17,10 @@ * under the License. */ -import { IUiSettingsClient } from './ui_settings_service'; +import { IUiSettingsClient } from './ui_settings_client'; +import { InternalUiSettingsServiceSetup } from './ui_settings_service'; -const createServiceMock = () => { +const createClientMock = () => { const mocked: jest.Mocked = { getDefaults: jest.fn(), get: jest.fn(), @@ -35,6 +36,18 @@ const createServiceMock = () => { return mocked; }; +const createSetupMock = () => { + const mocked: jest.Mocked = { + setDefaults: jest.fn(), + asScopedToClient: jest.fn(), + }; + + mocked.asScopedToClient.mockReturnValue(createClientMock()); + + return mocked; +}; + export const uiSettingsServiceMock = { - create: createServiceMock, + createSetup: createSetupMock, + createClient: createClientMock, }; diff --git a/src/core/server/ui_settings/ui_settings_service.test.mock.ts b/src/core/server/ui_settings/ui_settings_service.test.mock.ts new file mode 100644 index 0000000000000..586ad3049ed6a --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_service.test.mock.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const MockUiSettingsClientConstructor = jest.fn(); + +jest.doMock('./ui_settings_client', () => ({ + UiSettingsClient: MockUiSettingsClientConstructor, +})); diff --git a/src/core/server/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_service.test.ts new file mode 100644 index 0000000000000..832d61bdb4137 --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { BehaviorSubject } from 'rxjs'; + +import { MockUiSettingsClientConstructor } from './ui_settings_service.test.mock'; + +import { UiSettingsService } from './ui_settings_service'; +import { httpServiceMock } from '../http/http_service.mock'; +import { loggingServiceMock } from '../logging/logging_service.mock'; +import { SavedObjectsClientMock } from '../mocks'; +import { mockCoreContext } from '../core_context.mock'; + +const overrides = { + overrideBaz: 'baz', +}; + +const defaults = { + foo: { + name: 'foo', + value: 'bar', + category: [], + description: '', + }, +}; + +const coreContext = mockCoreContext.create(); +coreContext.configService.atPath.mockReturnValue(new BehaviorSubject({ overrides })); +const httpSetup = httpServiceMock.createSetupContract(); +const setupDeps = { http: httpSetup }; +const savedObjectsClient = SavedObjectsClientMock.create(); + +afterEach(() => { + MockUiSettingsClientConstructor.mockClear(); +}); + +describe('uiSettings', () => { + describe('#setup', () => { + describe('#asScopedToClient', () => { + it('passes overrides 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].overrides).toBe(overrides); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toEqual(overrides); + }); + + it('passes overrides with deprecated "server.defaultRoute"', async () => { + const service = new UiSettingsService(coreContext); + const httpSetupWithDefaultRoute = httpServiceMock.createSetupContract(); + httpSetupWithDefaultRoute.config.defaultRoute = '/defaultRoute'; + const setup = await service.setup({ http: httpSetupWithDefaultRoute }); + setup.asScopedToClient(savedObjectsClient); + + expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toEqual({ + ...overrides, + defaultRoute: '/defaultRoute', + }); + + expect(loggingServiceMock.collect(coreContext.logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "Config key \\"server.defaultRoute\\" is deprecated. It has been replaced with \\"uiSettings.overrides.defaultRoute\\"", + ], + ] + `); + }); + + it('passes a copy of set defaults to UiSettingsClient', async () => { + const service = new UiSettingsService(coreContext); + const setup = await service.setup(setupDeps); + + setup.setDefaults(defaults); + setup.asScopedToClient(savedObjectsClient); + expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); + + expect(MockUiSettingsClientConstructor.mock.calls[0][0].defaults).toEqual(defaults); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].defaults).not.toBe(defaults); + }); + }); + + describe('#setDefaults', () => { + it('throws if set defaults for 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"` + ); + }); + }); + }); +}); diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts new file mode 100644 index 0000000000000..746fa514c5d4b --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -0,0 +1,140 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { CoreService } from '../../types'; +import { CoreContext } from '../core_context'; +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 { mapToObject } from '../../utils/'; + +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; + /** 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): void; + /** + * Creates uiSettings client with provided *scoped* saved objects client {@link IUiSettingsClient} + * @param values + */ + asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; +} + +/** @internal */ +export class UiSettingsService implements CoreService { + private readonly log: Logger; + private readonly config$: Observable; + private readonly uiSettingsDefaults = new Map(); + + constructor(private readonly coreContext: CoreContext) { + this.log = coreContext.logger.get('ui-settings-service'); + this.config$ = coreContext.configService.atPath('uiSettings'); + } + + public async setup(deps: SetupDeps): Promise { + 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), + asScopedToClient: (savedObjectsClient: SavedObjectsClientContract) => { + return new UiSettingsClient({ + type: 'config', + id: version, + buildNum, + savedObjectsClient, + defaults: mapToObject(this.uiSettingsDefaults), + overrides, + log: this.log, + }); + }, + }; + } + + public async start() {} + + public async stop() {} + + private setDefaults(values: Record = {}) { + Object.entries(values).forEach(([key, value]) => { + if (this.uiSettingsDefaults.has(key)) { + throw new Error(`uiSettings defaults for key [${key}] has been already set`); + } + this.uiSettingsDefaults.set(key, value); + }); + } + + private async getOverrides(deps: SetupDeps) { + const config = await this.config$.pipe(first()).toPromise(); + const overrides: Record = config.overrides; + // manually implemented deprecation until New platform Config service + // supports them https://github.com/elastic/kibana/issues/40255 + if (typeof deps.http.config.defaultRoute !== 'undefined') { + overrides.defaultRoute = deps.http.config.defaultRoute; + this.log.warn( + 'Config key "server.defaultRoute" is deprecated. It has been replaced with "uiSettings.overrides.defaultRoute"' + ); + } + + return overrides; + } +} diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 1d74fc63bcc0f..3f9b897730f51 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -108,9 +108,7 @@ export default () => Joi.object({ ssl: HANDLED_IN_NEW_PLATFORM, }).default(), - uiSettings: Joi.object().keys({ - overrides: Joi.object().unknown(true).default() - }).default(), + uiSettings: HANDLED_IN_NEW_PLATFORM, logging: Joi.object().keys({ silent: Joi.boolean().default(false), diff --git a/src/legacy/server/config/transform_deprecations.js b/src/legacy/server/config/transform_deprecations.js index 8be880074f9fd..7cac17a88fe64 100644 --- a/src/legacy/server/config/transform_deprecations.js +++ b/src/legacy/server/config/transform_deprecations.js @@ -95,7 +95,6 @@ const cspRules = (settings, log) => { const deprecations = [ //server - rename('server.defaultRoute', 'uiSettings.overrides.defaultRoute'), unused('server.xsrf.token'), unused('uiSettings.enabled'), rename('optimize.lazy', 'optimize.watch'), diff --git a/src/legacy/server/config/transform_deprecations.test.js b/src/legacy/server/config/transform_deprecations.test.js index 4094443ac0006..f8cf38efc8bd8 100644 --- a/src/legacy/server/config/transform_deprecations.test.js +++ b/src/legacy/server/config/transform_deprecations.test.js @@ -62,24 +62,6 @@ describe('server/config', function () { }); }); - describe('server.defaultRoute', () => { - it('renames to uiSettings.overrides.defaultRoute when specified', () => { - const settings = { - server: { - defaultRoute: '/app/foo', - }, - }; - - expect(transformDeprecations(settings)).toEqual({ - uiSettings: { - overrides: { - defaultRoute: '/app/foo' - } - } - }); - }); - }); - describe('csp.rules', () => { describe('with nonce source', () => { it('logs a warning', () => { diff --git a/src/legacy/server/http/integration_tests/default_route_provider.test.ts b/src/legacy/server/http/integration_tests/default_route_provider.test.ts index fe8c464965132..d74be851a3535 100644 --- a/src/legacy/server/http/integration_tests/default_route_provider.test.ts +++ b/src/legacy/server/http/integration_tests/default_route_provider.test.ts @@ -45,11 +45,11 @@ describe('default route provider', () => { throw Error(`unsupported ui setting: ${key}`); }, getDefaults: () => { - return Promise.resolve({ + return { defaultRoute: { value: '/app/kibana', }, - }); + }; }, }; }); diff --git a/src/legacy/server/http/setup_default_route_provider.ts b/src/legacy/server/http/setup_default_route_provider.ts index 07ff61015a187..f627cf30a3cff 100644 --- a/src/legacy/server/http/setup_default_route_provider.ts +++ b/src/legacy/server/http/setup_default_route_provider.ts @@ -40,7 +40,7 @@ export function setupDefaultRouteProvider(server: Legacy.Server) { `Ignoring configured default route of '${defaultRoute}', as it is malformed.` ); - const fallbackRoute = (await uiSettings.getDefaults()).defaultRoute.value; + const fallbackRoute = uiSettings.getDefaults().defaultRoute.value; const qualifiedFallbackRoute = `${request.getBasePath()}${fallbackRoute}`; return qualifiedFallbackRoute; diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index f5771c6b86d9a..e7f2f4c85435f 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -28,6 +28,7 @@ import { LoggerFactory, SavedObjectsClientContract, SavedObjectsLegacyService, + IUiSettingsClient, PackageInfo, } from '../../core/server'; @@ -41,7 +42,6 @@ import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/ela import { CapabilitiesModifier } from './capabilities'; import { IndexPatternsServiceFactory } from './index_patterns'; import { Capabilities } from '../../core/public'; -import { IUiSettingsClient } from '../../legacy/ui/ui_settings/ui_settings_service'; import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; export interface KibanaConfig { @@ -104,6 +104,13 @@ type KbnMixinFunc = (kbnServer: KbnServer, server: Server, config: any) => Promi // eslint-disable-next-line import/no-default-export export default class KbnServer { public readonly newPlatform: { + __internals: { + hapiServer: LegacyServiceSetupDeps['core']['http']['server']; + uiPlugins: LegacyServiceSetupDeps['core']['plugins']['uiPlugins']; + elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch']; + uiSettings: LegacyServiceSetupDeps['core']['uiSettings']; + kibanaMigrator: LegacyServiceStartDeps['core']['savedObjects']['migrator']; + }; env: { mode: Readonly; packageInfo: Readonly; diff --git a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts index 3720677ec17d2..f9a5c25878322 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts +++ b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts @@ -28,7 +28,7 @@ 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 = await uiSettings.getDefaults(); + const uiSettingDefaults = uiSettings.getDefaults(); Object.keys(uiSettingDefaults).forEach(key => { if (has(uiConfigs, key) && uiSettingDefaults[key].type === 'json') { uiConfigs[key] = JSON.parse(uiConfigs[key]); diff --git a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts index f43c6436d1c33..9f553b37935d7 100644 --- a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts +++ b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts @@ -20,9 +20,7 @@ import sinon from 'sinon'; import expect from '@kbn/expect'; -// @ts-ignore -import { Config } from '../../../server/config'; - +import { SavedObjectsClientMock } from '../../../../core/server/mocks'; import * as uiSettingsServiceFactoryNS from '../ui_settings_service_factory'; import * as getUiSettingsServiceForRequestNS from '../ui_settings_service_for_request'; // @ts-ignore @@ -33,18 +31,16 @@ interface Decorators { request: { [name: string]: any }; } +const uiSettingDefaults = { + application: { + defaultProperty1: 'value1', + }, +}; + describe('uiSettingsMixin()', () => { const sandbox = sinon.createSandbox(); function setup() { - const config = Config.withDefaultSchema({ - uiSettings: { - overrides: { - foo: 'bar', - }, - }, - }); - // maps of decorations passed to `server.decorate()` const decorations: Decorators = { server: {}, @@ -55,7 +51,6 @@ describe('uiSettingsMixin()', () => { const server = { log: sinon.stub(), route: sinon.stub(), - config: () => config, addMemoizedFactoryToRequest(name: string, factory: (...args: any[]) => any) { this.decorate('request', name, function(this: typeof server) { return factory(this); @@ -73,12 +68,18 @@ describe('uiSettingsMixin()', () => { const kbnServer = { server, - config, - uiExports: { addConsumer: sinon.stub() }, + uiExports: { uiSettingDefaults }, ready: sinon.stub().returns(readyPromise), + newPlatform: { + __internals: { + uiSettings: { + setDefaults: sinon.stub(), + }, + }, + }, }; - uiSettingsMixin(kbnServer, server, config); + uiSettingsMixin(kbnServer, server); return { kbnServer, @@ -90,6 +91,15 @@ describe('uiSettingsMixin()', () => { afterEach(() => sandbox.restore()); + it('passes uiSettingsDefaults to the new platform', () => { + const { kbnServer } = setup(); + sinon.assert.calledOnce(kbnServer.newPlatform.__internals.uiSettings.setDefaults); + sinon.assert.calledWithExactly( + kbnServer.newPlatform.__internals.uiSettings.setDefaults, + uiSettingDefaults + ); + }); + describe('server.uiSettingsServiceFactory()', () => { it('decorates server with "uiSettingsServiceFactory"', () => { const { decorations } = setup(); @@ -116,18 +126,16 @@ describe('uiSettingsMixin()', () => { uiSettingsServiceFactoryNS, 'uiSettingsServiceFactory' ); + sinon.assert.notCalled(uiSettingsServiceFactoryStub); + + const savedObjectsClient = SavedObjectsClientMock.create(); decorations.server.uiSettingsServiceFactory({ - foo: 'bar', + savedObjectsClient, }); sinon.assert.calledOnce(uiSettingsServiceFactoryStub); sinon.assert.calledWithExactly(uiSettingsServiceFactoryStub, server as any, { - // @ts-ignore foo doesn't exist on Hapi.Server - foo: 'bar', - overrides: { - foo: 'bar', - }, - getDefaults: sinon.match.func, + savedObjectsClient, }); }); }); @@ -161,12 +169,7 @@ describe('uiSettingsMixin()', () => { sinon.assert.notCalled(getUiSettingsServiceForRequestStub); const request = {}; decorations.request.getUiSettingsService.call(request); - sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server as any, request as any, { - overrides: { - foo: 'bar', - }, - getDefaults: sinon.match.func, - }); + sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server as any, request as any); }); }); diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts b/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts index b076a2a86e166..ae0ef1c91411e 100644 --- a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts +++ b/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts @@ -18,12 +18,11 @@ */ import { UnwrapPromise } from '@kbn/utility-types'; -import { SavedObjectsClientContract } from 'src/core/server'; +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 { IUiSettingsClient } from '../../../ui_settings_service'; let kbnServer: KbnServer; let servers: ReturnType; diff --git a/src/legacy/ui/ui_settings/ui_settings_mixin.js b/src/legacy/ui/ui_settings/ui_settings_mixin.js index 3683ba5dd265e..8c7ef25c6f8d7 100644 --- a/src/legacy/ui/ui_settings/ui_settings_mixin.js +++ b/src/legacy/ui/ui_settings/ui_settings_mixin.js @@ -42,22 +42,15 @@ export function uiSettingsMixin(kbnServer, server) { acc[currentKey] = updatedDefaultSetting; return acc; }, {}); - const getDefaults = () => mergedUiSettingDefaults; - const overrides = kbnServer.config.get('uiSettings.overrides'); + + kbnServer.newPlatform.__internals.uiSettings.setDefaults(mergedUiSettingDefaults); server.decorate('server', 'uiSettingsServiceFactory', (options = {}) => { - return uiSettingsServiceFactory(server, { - getDefaults, - overrides, - ...options - }); + return uiSettingsServiceFactory(server, options); }); server.addMemoizedFactoryToRequest('getUiSettingsService', request => { - return getUiSettingsServiceForRequest(server, request, { - getDefaults, - overrides, - }); + return getUiSettingsServiceForRequest(server, request); }); server.decorate('server', 'uiSettings', () => { diff --git a/src/legacy/ui/ui_settings/ui_settings_service_factory.ts b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts index 9e1384494161c..ab4eb75e4b703 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service_factory.ts +++ b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts @@ -17,18 +17,13 @@ * under the License. */ import { Legacy } from 'kibana'; -import { - IUiSettingsClient, - UiSettingsService, - UiSettingsServiceOptions, -} from './ui_settings_service'; +import { IUiSettingsClient, SavedObjectsClientContract } from 'src/core/server'; -export type UiSettingsServiceFactoryOptions = Pick< - UiSettingsServiceOptions, - 'savedObjectsClient' | 'getDefaults' | 'overrides' ->; +export interface UiSettingsServiceFactoryOptions { + savedObjectsClient: SavedObjectsClientContract; +} /** - * Create an instance of UiSettingsService that will use the + * Create an instance of UiSettingsClient that will use the * passed `savedObjectsClient` to communicate with elasticsearch * * @return {IUiSettingsClient} @@ -37,17 +32,5 @@ export function uiSettingsServiceFactory( server: Legacy.Server, options: UiSettingsServiceFactoryOptions ): IUiSettingsClient { - const config = server.config(); - - const { savedObjectsClient, getDefaults, overrides } = options; - - return new UiSettingsService({ - type: 'config', - id: config.get('pkg.version'), - buildNum: config.get('pkg.buildNum'), - savedObjectsClient, - getDefaults, - overrides, - logWithMetadata: server.logWithMetadata, - }); + return server.newPlatform.__internals.uiSettings.asScopedToClient(options.savedObjectsClient); } diff --git a/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts b/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts index e265ad5f1e115..057fc64c9ebd7 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts +++ b/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts @@ -18,10 +18,9 @@ */ import { Legacy } from 'kibana'; +import { IUiSettingsClient } from 'src/core/server'; import { uiSettingsServiceFactory } from './ui_settings_service_factory'; -import { IUiSettingsClient, UiSettingsServiceOptions } from './ui_settings_service'; -type Options = Pick; /** * Get/create an instance of UiSettingsService bound to a specific request. * Each call is cached (keyed on the request object itself) and subsequent @@ -36,16 +35,8 @@ type Options = Pick; */ export function getUiSettingsServiceForRequest( server: Legacy.Server, - request: Legacy.Request, - options: Options + request: Legacy.Request ): IUiSettingsClient { - const { getDefaults, overrides } = options; - - const uiSettingsService = uiSettingsServiceFactory(server, { - getDefaults, - overrides, - savedObjectsClient: request.getSavedObjectsClient(), - }); - - return uiSettingsService; + const savedObjectsClient = request.getSavedObjectsClient(); + return uiSettingsServiceFactory(server, { savedObjectsClient }); } diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts index 6edac00b8a1ab..cfc33b4fad7ea 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -6,7 +6,7 @@ import { Legacy } from 'kibana'; import { setupRequest } from './setup_request'; -import { uiSettingsServiceMock } from '../../../../../../../src/legacy/ui/ui_settings/ui_settings_service.mock'; +import { uiSettingsServiceMock } from 'src/core/server/mocks'; function getMockRequest() { const callWithRequestSpy = jest.fn(); @@ -125,7 +125,7 @@ describe('setupRequest', () => { it('should set `ignore_throttled=true` if `includeFrozen=false`', async () => { const { mockRequest, callWithRequestSpy } = getMockRequest(); - const uiSettingsService = uiSettingsServiceMock.create(); + const uiSettingsService = uiSettingsServiceMock.createClient(); // mock includeFrozen to return false uiSettingsService.get.mockResolvedValue(false); mockRequest.getUiSettingsService = () => uiSettingsService; @@ -138,7 +138,7 @@ describe('setupRequest', () => { it('should set `ignore_throttled=false` if `includeFrozen=true`', async () => { const { mockRequest, callWithRequestSpy } = getMockRequest(); - const uiSettingsService = uiSettingsServiceMock.create(); + const uiSettingsService = uiSettingsServiceMock.createClient(); // mock includeFrozen to return true uiSettingsService.get.mockResolvedValue(true); mockRequest.getUiSettingsService = () => uiSettingsService; diff --git a/x-pack/legacy/plugins/ml/common/types/modules.ts b/x-pack/legacy/plugins/ml/common/types/modules.ts index cb012c3641f3b..18879304d7c7f 100644 --- a/x-pack/legacy/plugins/ml/common/types/modules.ts +++ b/x-pack/legacy/plugins/ml/common/types/modules.ts @@ -3,9 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { SavedObjectAttributes } from 'src/core/server/types'; import { Datafeed, Job } from '../../public/jobs/new_job_new/common/job_creator/configs'; -import { SavedObjectAttributes } from '../../../../../../target/types/core/server'; export interface ModuleJob { id: string;