| LegacyRequest, requestSpecificBasePath: string) => void;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.irenderoptions.includeusersettings.md b/docs/development/core/server/kibana-plugin-server.irenderoptions.includeusersettings.md
new file mode 100644
index 0000000000000..cedf3d27d0887
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.irenderoptions.includeusersettings.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IRenderOptions](./kibana-plugin-server.irenderoptions.md) > [includeUserSettings](./kibana-plugin-server.irenderoptions.includeusersettings.md)
+
+## IRenderOptions.includeUserSettings property
+
+Set whether to output user settings in the page metadata. `true` by default.
+
+Signature:
+
+```typescript
+includeUserSettings?: boolean;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.irenderoptions.md b/docs/development/core/server/kibana-plugin-server.irenderoptions.md
new file mode 100644
index 0000000000000..34bed8b5e078c
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.irenderoptions.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IRenderOptions](./kibana-plugin-server.irenderoptions.md)
+
+## IRenderOptions interface
+
+
+Signature:
+
+```typescript
+export interface IRenderOptions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [includeUserSettings](./kibana-plugin-server.irenderoptions.includeusersettings.md) | boolean
| Set whether to output user settings in the page metadata. true
by default. |
+
diff --git a/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md b/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md
index 238424b1df1d5..ff71f13466cf8 100644
--- a/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md
+++ b/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md
@@ -1,13 +1,13 @@
-
-
-[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IRouter](./kibana-plugin-server.irouter.md) > [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md)
-
-## IRouter.handleLegacyErrors property
-
-Wrap a router handler to catch and converts legacy boom errors to proper custom errors.
-
-Signature:
-
-```typescript
-(handler: RequestHandler
) => RequestHandler
;
-```
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IRouter](./kibana-plugin-server.irouter.md) > [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md)
+
+## IRouter.handleLegacyErrors property
+
+Wrap a router handler to catch and converts legacy boom errors to proper custom errors.
+
+Signature:
+
+```typescript
+handleLegacyErrors:
(handler: RequestHandler
) => RequestHandler
;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.md b/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.md
new file mode 100644
index 0000000000000..2e6daa58db25f
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IScopedRenderingClient](./kibana-plugin-server.iscopedrenderingclient.md)
+
+## IScopedRenderingClient interface
+
+
+Signature:
+
+```typescript
+export interface IScopedRenderingClient
+```
+
+## Methods
+
+| Method | Description |
+| --- | --- |
+| [render(options)](./kibana-plugin-server.iscopedrenderingclient.render.md) | Generate a KibanaResponse
which renders an HTML page bootstrapped with the core
bundle. Intended as a response body for HTTP route handlers. |
+
diff --git a/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md b/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md
new file mode 100644
index 0000000000000..1bc78dd84571d
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md
@@ -0,0 +1,41 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IScopedRenderingClient](./kibana-plugin-server.iscopedrenderingclient.md) > [render](./kibana-plugin-server.iscopedrenderingclient.render.md)
+
+## IScopedRenderingClient.render() method
+
+Generate a `KibanaResponse` which renders an HTML page bootstrapped with the `core` bundle. Intended as a response body for HTTP route handlers.
+
+Signature:
+
+```typescript
+render(options?: IRenderOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| options | IRenderOptions
| |
+
+Returns:
+
+`Promise`
+
+## Example
+
+
+```ts
+router.get(
+ { path: '/', validate: false },
+ (context, request, response) =>
+ response.ok({
+ body: await context.core.rendering.render(),
+ headers: {
+ 'content-security-policy': context.core.http.csp.header,
+ },
+ })
+);
+
+```
+
diff --git a/docs/development/core/server/kibana-plugin-server.legacyservicesetupdeps.core.md b/docs/development/core/server/kibana-plugin-server.legacyservicesetupdeps.core.md
index 09ebf1170715b..c4c043a903d06 100644
--- a/docs/development/core/server/kibana-plugin-server.legacyservicesetupdeps.core.md
+++ b/docs/development/core/server/kibana-plugin-server.legacyservicesetupdeps.core.md
@@ -7,7 +7,5 @@
Signature:
```typescript
-core: InternalCoreSetup & {
- plugins: PluginsServiceSetup;
- };
+core: LegacyCoreSetup;
```
diff --git a/docs/development/core/server/kibana-plugin-server.legacyservicesetupdeps.md b/docs/development/core/server/kibana-plugin-server.legacyservicesetupdeps.md
index 4475318522dfa..7961cedd2c054 100644
--- a/docs/development/core/server/kibana-plugin-server.legacyservicesetupdeps.md
+++ b/docs/development/core/server/kibana-plugin-server.legacyservicesetupdeps.md
@@ -18,6 +18,6 @@ export interface LegacyServiceSetupDeps
| Property | Type | Description |
| --- | --- | --- |
-| [core](./kibana-plugin-server.legacyservicesetupdeps.core.md) | InternalCoreSetup & {
plugins: PluginsServiceSetup;
}
| |
+| [core](./kibana-plugin-server.legacyservicesetupdeps.core.md) | LegacyCoreSetup
| |
| [plugins](./kibana-plugin-server.legacyservicesetupdeps.plugins.md) | Record<string, unknown>
| |
diff --git a/docs/development/core/server/kibana-plugin-server.legacyservicestartdeps.core.md b/docs/development/core/server/kibana-plugin-server.legacyservicestartdeps.core.md
index c5cf473aaa01a..47018f4594967 100644
--- a/docs/development/core/server/kibana-plugin-server.legacyservicestartdeps.core.md
+++ b/docs/development/core/server/kibana-plugin-server.legacyservicestartdeps.core.md
@@ -7,7 +7,5 @@
Signature:
```typescript
-core: InternalCoreStart & {
- plugins: PluginsServiceStart;
- };
+core: LegacyCoreStart;
```
diff --git a/docs/development/core/server/kibana-plugin-server.legacyservicestartdeps.md b/docs/development/core/server/kibana-plugin-server.legacyservicestartdeps.md
index 801138b64e46a..602fe5356d525 100644
--- a/docs/development/core/server/kibana-plugin-server.legacyservicestartdeps.md
+++ b/docs/development/core/server/kibana-plugin-server.legacyservicestartdeps.md
@@ -18,6 +18,6 @@ export interface LegacyServiceStartDeps
| Property | Type | Description |
| --- | --- | --- |
-| [core](./kibana-plugin-server.legacyservicestartdeps.core.md) | InternalCoreStart & {
plugins: PluginsServiceStart;
}
| |
+| [core](./kibana-plugin-server.legacyservicestartdeps.core.md) | LegacyCoreStart
| |
| [plugins](./kibana-plugin-server.legacyservicestartdeps.plugins.md) | Record<string, unknown>
| |
diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md
index 9c8aafb158bfd..5e7f84c55244d 100644
--- a/docs/development/core/server/kibana-plugin-server.md
+++ b/docs/development/core/server/kibana-plugin-server.md
@@ -70,7 +70,9 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [IKibanaResponse](./kibana-plugin-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution |
| [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. |
| [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | |
+| [IRenderOptions](./kibana-plugin-server.irenderoptions.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. |
+| [IScopedRenderingClient](./kibana-plugin-server.iscopedrenderingclient.md) | |
| [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) | |
@@ -91,7 +93,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [PluginManifest](./kibana-plugin-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. |
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | |
-| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request |
+| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [rendering](./kibana-plugin-server.iscopedrenderingclient.md) - Rendering client which uses the data of the incoming request - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request |
| [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. |
| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. |
| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route |
diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md
index 2d8b27ecb6c67..d1760dafd5bb6 100644
--- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md
+++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md
@@ -8,6 +8,7 @@
```typescript
core: {
+ rendering: IScopedRenderingClient;
savedObjects: {
client: SavedObjectsClientContract;
};
diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md
index d9b781e1e550e..7c8625a5824ee 100644
--- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md
+++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md
@@ -6,7 +6,7 @@
Plugin specific context passed to a route handler.
-Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request
+Provides the following clients: - [rendering](./kibana-plugin-server.iscopedrenderingclient.md) - Rendering client which uses the data of the incoming request - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request
Signature:
@@ -18,5 +18,5 @@ export interface RequestHandlerContext
| Property | Type | Description |
| --- | --- | --- |
-| [core](./kibana-plugin-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
uiSettings: {
client: IUiSettingsClient;
};
}
| |
+| [core](./kibana-plugin-server.requesthandlercontext.core.md) | {
rendering: IScopedRenderingClient;
savedObjects: {
client: SavedObjectsClientContract;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
uiSettings: {
client: IUiSettingsClient;
};
}
| |
diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md b/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md
index 4fbcf0981f114..23a72fc3c68b3 100644
--- a/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md
+++ b/docs/development/core/server/kibana-plugin-server.routeconfig.validate.md
@@ -1,62 +1,62 @@
-
-
-[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfig](./kibana-plugin-server.routeconfig.md) > [validate](./kibana-plugin-server.routeconfig.validate.md)
-
-## RouteConfig.validate property
-
-A schema created with `@kbn/config-schema` that every request will be validated against.
-
-Signature:
-
-```typescript
-RouteValidatorFullConfig | false;
-```
-
-## Remarks
-
-You \*must\* specify a validation schema to be able to read: - url path segments - request query - request body To opt out of validating the request, specify `validate: false`. In this case request params, query, and body will be \*\*empty\*\* objects and have no access to raw values. In some cases you may want to use another validation library. To do this, you need to instruct the `@kbn/config-schema` library to output \*\*non-validated values\*\* with setting schema as `schema.object({}, { allowUnknowns: true })`;
-
-## Example
-
-
-```ts
- import { schema } from '@kbn/config-schema';
- router.get({
- path: 'path/{id}',
- validate: {
- params: schema.object({
- id: schema.string(),
- }),
- query: schema.object({...}),
- body: schema.object({...}),
- },
-},
-(context, req, res,) {
- req.params; // type Readonly<{id: string}>
- console.log(req.params.id); // value
-});
-
-router.get({
- path: 'path/{id}',
- validate: false, // handler has no access to params, query, body values.
-},
-(context, req, res,) {
- req.params; // type Readonly<{}>;
- console.log(req.params.id); // undefined
-});
-
-router.get({
- path: 'path/{id}',
- validate: {
- // handler has access to raw non-validated params in runtime
- params: schema.object({}, { allowUnknowns: true })
- },
-},
-(context, req, res,) {
- req.params; // type Readonly<{}>;
- console.log(req.params.id); // value
- myValidationLibrary.validate({ params: req.params });
-});
-
-```
-
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfig](./kibana-plugin-server.routeconfig.md) > [validate](./kibana-plugin-server.routeconfig.validate.md)
+
+## RouteConfig.validate property
+
+A schema created with `@kbn/config-schema` that every request will be validated against.
+
+Signature:
+
+```typescript
+validate: RouteValidatorFullConfig
| false;
+```
+
+## Remarks
+
+You \*must\* specify a validation schema to be able to read: - url path segments - request query - request body To opt out of validating the request, specify `validate: false`. In this case request params, query, and body will be \*\*empty\*\* objects and have no access to raw values. In some cases you may want to use another validation library. To do this, you need to instruct the `@kbn/config-schema` library to output \*\*non-validated values\*\* with setting schema as `schema.object({}, { allowUnknowns: true })`;
+
+## Example
+
+
+```ts
+ import { schema } from '@kbn/config-schema';
+ router.get({
+ path: 'path/{id}',
+ validate: {
+ params: schema.object({
+ id: schema.string(),
+ }),
+ query: schema.object({...}),
+ body: schema.object({...}),
+ },
+},
+(context, req, res,) {
+ req.params; // type Readonly<{id: string}>
+ console.log(req.params.id); // value
+});
+
+router.get({
+ path: 'path/{id}',
+ validate: false, // handler has no access to params, query, body values.
+},
+(context, req, res,) {
+ req.params; // type Readonly<{}>;
+ console.log(req.params.id); // undefined
+});
+
+router.get({
+ path: 'path/{id}',
+ validate: {
+ // handler has access to raw non-validated params in runtime
+ params: schema.object({}, { allowUnknowns: true })
+ },
+},
+(context, req, res,) {
+ req.params; // type Readonly<{}>;
+ console.log(req.params.id); // value
+ myValidationLibrary.validate({ params: req.params });
+});
+
+```
+
diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationerror._constructor_.md b/docs/development/core/server/kibana-plugin-server.routevalidationerror._constructor_.md
index 31dc6ceb91995..551e13faaf154 100644
--- a/docs/development/core/server/kibana-plugin-server.routevalidationerror._constructor_.md
+++ b/docs/development/core/server/kibana-plugin-server.routevalidationerror._constructor_.md
@@ -1,21 +1,21 @@
-
-
-[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationError](./kibana-plugin-server.routevalidationerror.md) > [(constructor)](./kibana-plugin-server.routevalidationerror._constructor_.md)
-
-## RouteValidationError.(constructor)
-
-Constructs a new instance of the `RouteValidationError` class
-
-Signature:
-
-```typescript
-constructor(error;: Error | string, path?: string[];)
-```
-
-## Parameters
-
-| Parameter | Type | Description |
-| --- | --- | --- |
-| error | Error | string
| |
-| path | string[]
| |
-
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationError](./kibana-plugin-server.routevalidationerror.md) > [(constructor)](./kibana-plugin-server.routevalidationerror._constructor_.md)
+
+## RouteValidationError.(constructor)
+
+Constructs a new instance of the `RouteValidationError` class
+
+Signature:
+
+```typescript
+constructor(error: Error | string, path?: string[]);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| error | Error | string
| |
+| path | string[]
| |
+
diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.badrequest.md b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.badrequest.md
index 2462ae17943be..36ea6103fb352 100644
--- a/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.badrequest.md
+++ b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.badrequest.md
@@ -1,13 +1,13 @@
-
-
-[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) > [badRequest](./kibana-plugin-server.routevalidationresultfactory.badrequest.md)
-
-## RouteValidationResultFactory.badRequest property
-
-Signature:
-
-```typescript
-(error: Error | string, path?: string[]) => {
- RouteValidationError;
- };
-```
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) > [badRequest](./kibana-plugin-server.routevalidationresultfactory.badrequest.md)
+
+## RouteValidationResultFactory.badRequest property
+
+Signature:
+
+```typescript
+badRequest: (error: Error | string, path?: string[]) => {
+ error: RouteValidationError;
+ };
+```
diff --git a/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.ok.md b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.ok.md
index c86ef616de103..eca6a31bd547f 100644
--- a/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.ok.md
+++ b/docs/development/core/server/kibana-plugin-server.routevalidationresultfactory.ok.md
@@ -1,13 +1,13 @@
-
-
-[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) > [ok](./kibana-plugin-server.routevalidationresultfactory.ok.md)
-
-## RouteValidationResultFactory.ok property
-
-Signature:
-
-```typescript
-(value: T) => {
- T;
- };
-```
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) > [ok](./kibana-plugin-server.routevalidationresultfactory.ok.md)
+
+## RouteValidationResultFactory.ok property
+
+Signature:
+
+```typescript
+ok: (value: T) => {
+ value: T;
+ };
+```
diff --git a/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.unsafe.md b/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.unsafe.md
index b1c75e6dbdf67..0406a372c4e9d 100644
--- a/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.unsafe.md
+++ b/docs/development/core/server/kibana-plugin-server.routevalidatoroptions.unsafe.md
@@ -1,18 +1,17 @@
-
-
-[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidatorOptions](./kibana-plugin-server.routevalidatoroptions.md) > [unsafe](./kibana-plugin-server.routevalidatoroptions.unsafe.md)
-
-## RouteValidatorOptions.unsafe property
-
-Set the `unsafe` config to avoid running some additional internal \*safe\* validations on top of your custom validation
-
-Signature:
-
-```typescript
-unsafe?: {
- params?: boolean;
- query?: boolean;
- body?: boolean;
- }
-
-```
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteValidatorOptions](./kibana-plugin-server.routevalidatoroptions.md) > [unsafe](./kibana-plugin-server.routevalidatoroptions.unsafe.md)
+
+## RouteValidatorOptions.unsafe property
+
+Set the `unsafe` config to avoid running some additional internal \*safe\* validations on top of your custom validation
+
+Signature:
+
+```typescript
+unsafe?: {
+ params?: boolean;
+ query?: boolean;
+ body?: boolean;
+ };
+```
diff --git a/src/cli/cluster/cluster_manager.ts b/src/cli/cluster/cluster_manager.ts
index d97f7485fb4d2..3fa4bdcbc5fa5 100644
--- a/src/cli/cluster/cluster_manager.ts
+++ b/src/cli/cluster/cluster_manager.ts
@@ -26,7 +26,7 @@ import { first, mapTo, filter, map, take } from 'rxjs/operators';
import { REPO_ROOT } from '@kbn/dev-utils';
import { FSWatcher } from 'chokidar';
-import { LegacyConfig } from '../../core/server/legacy/config';
+import { LegacyConfig } from '../../core/server/legacy';
import { BasePathProxyServer } from '../../core/server/http';
// @ts-ignore
diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts
index 6a44000bf617e..0bde1b68e1876 100644
--- a/src/core/public/injected_metadata/injected_metadata_service.ts
+++ b/src/core/public/injected_metadata/injected_metadata_service.ts
@@ -80,9 +80,6 @@ export interface InjectedMetadataParams {
user?: Record;
};
};
- apm: {
- [key: string]: unknown;
- };
};
}
diff --git a/src/legacy/ui/ui_nav_links/ui_nav_links_mixin.js b/src/core/server/config/config.mock.ts
similarity index 61%
rename from src/legacy/ui/ui_nav_links/ui_nav_links_mixin.js
rename to src/core/server/config/config.mock.ts
index e445f5e9126d4..e098fa142b9d1 100644
--- a/src/legacy/ui/ui_nav_links/ui_nav_links_mixin.js
+++ b/src/core/server/config/config.mock.ts
@@ -17,18 +17,18 @@
* under the License.
*/
-import { UiNavLink } from './ui_nav_link';
+import { Config } from './config';
-export function uiNavLinksMixin(kbnServer, server) {
- const uiApps = server.getAllUiApps();
+type ConfigMock = jest.Mocked;
- const { navLinkSpecs = [] } = kbnServer.uiExports;
+const createConfigMock = (): ConfigMock => ({
+ has: jest.fn(),
+ get: jest.fn(),
+ set: jest.fn(),
+ getFlattenedPaths: jest.fn(),
+ toRaw: jest.fn(),
+});
- const fromSpecs = navLinkSpecs.map(navLinkSpec => new UiNavLink(navLinkSpec));
-
- const fromApps = uiApps.map(app => app.getNavLink()).filter(Boolean);
-
- const uiNavLinks = fromSpecs.concat(fromApps).sort((a, b) => a.getOrder() - b.getOrder());
-
- server.decorate('server', 'getUiNavLinks', () => uiNavLinks.slice(0));
-}
+export const configMock = {
+ create: createConfigMock,
+};
diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts
index 1668b409050b7..700ae04f00d47 100644
--- a/src/core/server/http/http_service.mock.ts
+++ b/src/core/server/http/http_service.mock.ts
@@ -20,6 +20,7 @@
import { Server } from 'hapi';
import { CspConfig } from '../csp';
import { mockRouter } from './router/router.mock';
+import { configMock } from '../config/config.mock';
import { InternalHttpServiceSetup } from './types';
import { HttpService } from './http_service';
import { OnPreAuthToolkit } from './lifecycle/on_pre_auth';
@@ -28,13 +29,14 @@ import { sessionStorageMock } from './cookie_session_storage.mocks';
import { OnPostAuthToolkit } from './lifecycle/on_post_auth';
import { OnPreResponseToolkit } from './lifecycle/on_pre_response';
+type BasePathMocked = jest.Mocked;
export type HttpServiceSetupMock = jest.Mocked & {
- basePath: jest.Mocked;
+ basePath: BasePathMocked;
};
-const createBasePathMock = (): jest.Mocked => ({
- serverBasePath: '/mock-server-basepath',
- get: jest.fn(),
+const createBasePathMock = (serverBasePath = '/mock-server-basepath'): BasePathMocked => ({
+ serverBasePath,
+ get: jest.fn().mockReturnValue(serverBasePath),
set: jest.fn(),
prepend: jest.fn(),
remove: jest.fn(),
@@ -44,9 +46,12 @@ const createSetupContractMock = () => {
const setupContract: HttpServiceSetupMock = {
// we can mock other hapi server methods when we need it
server: ({
+ name: 'http-server-test',
+ version: 'kibana',
route: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
+ config: jest.fn().mockReturnValue(configMock.create()),
} as unknown) as jest.MockedClass,
createCookieSessionStorageFactory: jest.fn(),
registerOnPreAuth: jest.fn(),
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index 878f854f2a517..953fa0738597c 100644
--- a/src/core/server/index.ts
+++ b/src/core/server/index.ts
@@ -41,6 +41,7 @@
import { ElasticsearchServiceSetup, IScopedClusterClient } from './elasticsearch';
import { HttpServiceSetup } from './http';
+import { IScopedRenderingClient } from './rendering';
import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins';
import { ContextSetup } from './context';
import { IUiSettingsClient, UiSettingsServiceSetup, UiSettingsServiceStart } from './ui_settings';
@@ -149,6 +150,7 @@ export {
SessionCookieValidationResult,
SessionStorageFactory,
} from './http';
+export { RenderingServiceSetup, IRenderOptions, LegacyRenderOptions } from './rendering';
export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging';
export {
@@ -229,12 +231,21 @@ export {
SavedObjectsMigrationVersion,
} from './types';
-export { LegacyServiceSetupDeps, LegacyServiceStartDeps } from './legacy';
+export {
+ LegacyServiceSetupDeps,
+ LegacyServiceStartDeps,
+ LegacyServiceDiscoverPlugins,
+ LegacyConfig,
+ LegacyUiExports,
+ LegacyInternals,
+} from './legacy';
/**
* Plugin specific context passed to a route handler.
*
* Provides the following clients:
+ * - {@link IScopedRenderingClient | rendering} - Rendering client
+ * which uses the data of the incoming request
* - {@link SavedObjectsClient | savedObjects.client} - Saved Objects client
* which uses the credentials of the incoming request
* - {@link ScopedClusterClient | elasticsearch.dataClient} - Elasticsearch
@@ -248,6 +259,7 @@ export { LegacyServiceSetupDeps, LegacyServiceStartDeps } from './legacy';
*/
export interface RequestHandlerContext {
core: {
+ rendering: IScopedRenderingClient;
savedObjects: {
client: SavedObjectsClientContract;
};
@@ -301,6 +313,7 @@ export {
CapabilitiesSetup,
CapabilitiesStart,
ContextSetup,
+ IScopedRenderingClient,
PluginsServiceSetup,
PluginsServiceStart,
PluginOpaqueId,
diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts
index 52adaaccab4b7..be4d830c55eab 100644
--- a/src/core/server/internal_types.ts
+++ b/src/core/server/internal_types.ts
@@ -17,15 +17,15 @@
* under the License.
*/
+import { CapabilitiesSetup, CapabilitiesStart } from './capabilities';
+import { ContextSetup } from './context';
import { InternalElasticsearchServiceSetup } from './elasticsearch';
import { InternalHttpServiceSetup } from './http';
-import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart } from './ui_settings';
-import { ContextSetup } from './context';
import {
- InternalSavedObjectsServiceStart,
InternalSavedObjectsServiceSetup,
+ InternalSavedObjectsServiceStart,
} from './saved_objects';
-import { CapabilitiesSetup, CapabilitiesStart } from './capabilities';
+import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart } from './ui_settings';
import { UuidServiceSetup } from './uuid';
/** @internal */
diff --git a/src/core/server/legacy/config/ensure_valid_configuration.ts b/src/core/server/legacy/config/ensure_valid_configuration.ts
index 026683a7b7cb0..a68d3df577a89 100644
--- a/src/core/server/legacy/config/ensure_valid_configuration.ts
+++ b/src/core/server/legacy/config/ensure_valid_configuration.ts
@@ -19,7 +19,7 @@
import { getUnusedConfigKeys } from './get_unused_config_keys';
import { ConfigService } from '../../config';
-import { LegacyServiceDiscoverPlugins } from '../legacy_service';
+import { LegacyServiceDiscoverPlugins } from '../types';
import { CriticalError } from '../../errors';
export async function ensureValidConfiguration(
diff --git a/src/core/server/legacy/config/get_unused_config_keys.test.ts b/src/core/server/legacy/config/get_unused_config_keys.test.ts
index bf011fa01a342..c4452fc6a1209 100644
--- a/src/core/server/legacy/config/get_unused_config_keys.test.ts
+++ b/src/core/server/legacy/config/get_unused_config_keys.test.ts
@@ -17,8 +17,7 @@
* under the License.
*/
-import { LegacyPluginSpec } from '../plugins/find_legacy_plugin_specs';
-import { LegacyConfig } from './types';
+import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types';
import { getUnusedConfigKeys } from './get_unused_config_keys';
describe('getUnusedConfigKeys', () => {
@@ -26,7 +25,7 @@ describe('getUnusedConfigKeys', () => {
jest.resetAllMocks();
});
- const getConfig = (values: Record = {}): LegacyConfig =>
+ const getConfig = (values: LegacyVars = {}): LegacyConfig =>
({
get: () => values as any,
} as LegacyConfig);
diff --git a/src/core/server/legacy/config/get_unused_config_keys.ts b/src/core/server/legacy/config/get_unused_config_keys.ts
index 73cc7d8c50474..e425082ba126d 100644
--- a/src/core/server/legacy/config/get_unused_config_keys.ts
+++ b/src/core/server/legacy/config/get_unused_config_keys.ts
@@ -22,8 +22,7 @@ import { difference, get, set } from 'lodash';
import { getTransform } from '../../../../legacy/deprecation/index';
import { unset, getFlattenedObject } from '../../../../legacy/utils';
import { hasConfigPathIntersection } from '../../config';
-import { LegacyPluginSpec } from '../plugins/find_legacy_plugin_specs';
-import { LegacyConfig } from './types';
+import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types';
const getFlattenedKeys = (object: object) => Object.keys(getFlattenedObject(object));
@@ -37,7 +36,7 @@ export async function getUnusedConfigKeys({
coreHandledConfigPaths: string[];
pluginSpecs: LegacyPluginSpec[];
disabledPluginSpecs: LegacyPluginSpec[];
- settings: Record;
+ settings: LegacyVars;
legacyConfig: LegacyConfig;
}) {
// transform deprecated plugin settings
diff --git a/src/core/server/legacy/config/index.ts b/src/core/server/legacy/config/index.ts
index c3f308fd6d903..f10e3f22d53c5 100644
--- a/src/core/server/legacy/config/index.ts
+++ b/src/core/server/legacy/config/index.ts
@@ -20,9 +20,3 @@
export { ensureValidConfiguration } from './ensure_valid_configuration';
export { LegacyObjectToConfigAdapter } from './legacy_object_to_config_adapter';
export { convertLegacyDeprecationProvider } from './legacy_deprecation_adapters';
-export {
- LegacyConfig,
- LegacyConfigDeprecation,
- LegacyConfigDeprecationFactory,
- LegacyConfigDeprecationProvider,
-} from './types';
diff --git a/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts b/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts
index 144e057c118f7..8651d05064492 100644
--- a/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts
+++ b/src/core/server/legacy/config/legacy_deprecation_adapters.test.ts
@@ -17,11 +17,11 @@
* under the License.
*/
-import { convertLegacyDeprecationProvider } from './legacy_deprecation_adapters';
-import { LegacyConfigDeprecationProvider } from './types';
import { ConfigDeprecation } from '../../config';
import { configDeprecationFactory } from '../../config/deprecation/deprecation_factory';
import { applyDeprecations } from '../../config/deprecation/apply_deprecations';
+import { LegacyConfigDeprecationProvider } from '../types';
+import { convertLegacyDeprecationProvider } from './legacy_deprecation_adapters';
jest.spyOn(configDeprecationFactory, 'unusedFromRoot');
jest.spyOn(configDeprecationFactory, 'renameFromRoot');
diff --git a/src/core/server/legacy/config/legacy_deprecation_adapters.ts b/src/core/server/legacy/config/legacy_deprecation_adapters.ts
index b0e3bc37e1510..1e0733969e662 100644
--- a/src/core/server/legacy/config/legacy_deprecation_adapters.ts
+++ b/src/core/server/legacy/config/legacy_deprecation_adapters.ts
@@ -18,8 +18,8 @@
*/
import { ConfigDeprecation, ConfigDeprecationProvider } from '../../config/deprecation';
-import { LegacyConfigDeprecation, LegacyConfigDeprecationProvider } from './index';
import { configDeprecationFactory } from '../../config/deprecation/deprecation_factory';
+import { LegacyConfigDeprecation, LegacyConfigDeprecationProvider } from '../types';
const convertLegacyDeprecation = (
legacyDeprecation: LegacyConfigDeprecation
diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts
index ffcbfda4e024d..bdcde8262ef98 100644
--- a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts
+++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts
@@ -19,6 +19,7 @@
import { ConfigPath } from '../../config';
import { ObjectToConfigAdapter } from '../../config/object_to_config_adapter';
+import { LegacyVars } from '../types';
/**
* Represents logging config supported by the legacy platform.
@@ -77,7 +78,7 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter {
};
}
- private static transformPlugins(configValue: Record) {
+ private static transformPlugins(configValue: LegacyVars) {
// These properties are the only ones we use from the existing `plugins` config node
// since `scanDirs` isn't respected by new platform plugin discovery.
return {
@@ -94,7 +95,7 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter {
case 'server':
return LegacyObjectToConfigAdapter.transformServer(configValue);
case 'plugins':
- return LegacyObjectToConfigAdapter.transformPlugins(configValue as Record);
+ return LegacyObjectToConfigAdapter.transformPlugins(configValue as LegacyVars);
default:
return configValue;
}
diff --git a/src/core/server/legacy/config/types.ts b/src/core/server/legacy/config/types.ts
deleted file mode 100644
index cac1002d6c244..0000000000000
--- a/src/core/server/legacy/config/types.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * New platform representation of the legacy configuration (KibanaConfig)
- *
- * @internal
- */
-export interface LegacyConfig {
- get(key?: string): T;
- has(key: string): boolean;
- set(key: string, value: any): void;
- set(config: Record): void;
-}
-
-/**
- * Representation of a legacy configuration deprecation factory used for
- * legacy plugin deprecations.
- *
- * @internal
- */
-export interface LegacyConfigDeprecationFactory {
- rename(oldKey: string, newKey: string): LegacyConfigDeprecation;
- unused(unusedKey: string): LegacyConfigDeprecation;
-}
-
-/**
- * Representation of a legacy configuration deprecation.
- *
- * @internal
- */
-export type LegacyConfigDeprecation = (
- settings: Record,
- log: (msg: string) => void
-) => void;
-
-/**
- * Representation of a legacy configuration deprecation provider.
- *
- * @internal
- */
-export type LegacyConfigDeprecationProvider = (
- factory: LegacyConfigDeprecationFactory
-) => LegacyConfigDeprecation[] | Promise;
diff --git a/src/core/server/legacy/index.ts b/src/core/server/legacy/index.ts
index 10686fc521d35..208e9b1167253 100644
--- a/src/core/server/legacy/index.ts
+++ b/src/core/server/legacy/index.ts
@@ -18,6 +18,10 @@
*/
/** @internal */
-export { LegacyObjectToConfigAdapter, ensureValidConfiguration, LegacyConfig } from './config';
+export { LegacyObjectToConfigAdapter, ensureValidConfiguration } from './config';
/** @internal */
-export { LegacyService, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './legacy_service';
+export { LegacyInternals } from './legacy_internals';
+/** @internal */
+export { LegacyService, ILegacyService } from './legacy_service';
+/** @internal */
+export * from './types';
diff --git a/src/core/server/legacy/legacy_internals.test.ts b/src/core/server/legacy/legacy_internals.test.ts
new file mode 100644
index 0000000000000..dcab62627442b
--- /dev/null
+++ b/src/core/server/legacy/legacy_internals.test.ts
@@ -0,0 +1,211 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Server } from 'hapi';
+
+import { configMock } from '../config/config.mock';
+import { httpServiceMock } from '../http/http_service.mock';
+import { httpServerMock } from '../http/http_server.mocks';
+import { findLegacyPluginSpecsMock } from './legacy_service.test.mocks';
+import { LegacyInternals } from './legacy_internals';
+import { ILegacyInternals, LegacyConfig, LegacyVars, LegacyUiExports } from './types';
+
+function varsProvider(vars: LegacyVars, configValue?: any) {
+ return {
+ fn: jest.fn().mockReturnValue(vars),
+ pluginSpec: {
+ readConfigValue: jest.fn().mockReturnValue(configValue),
+ },
+ };
+}
+
+describe('LegacyInternals', () => {
+ describe('getInjectedUiAppVars()', () => {
+ let uiExports: LegacyUiExports;
+ let config: LegacyConfig;
+ let server: Server;
+ let legacyInternals: ILegacyInternals;
+
+ beforeEach(async () => {
+ uiExports = findLegacyPluginSpecsMock().uiExports;
+ config = configMock.create() as any;
+ server = httpServiceMock.createSetupContract().server;
+ legacyInternals = new LegacyInternals(uiExports, config, server);
+ });
+
+ it('gets with no injectors', async () => {
+ await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(
+ `Object {}`
+ );
+ });
+
+ it('gets with no matching injectors', async () => {
+ const injector = jest.fn().mockResolvedValue({ not: 'core' });
+ legacyInternals.injectUiAppVars('not-core', injector);
+
+ await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(
+ `Object {}`
+ );
+ expect(injector).not.toHaveBeenCalled();
+ });
+
+ it('gets with single matching injector', async () => {
+ const injector = jest.fn().mockResolvedValue({ is: 'core' });
+ legacyInternals.injectUiAppVars('core', injector);
+
+ await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(`
+ Object {
+ "is": "core",
+ }
+ `);
+ expect(injector).toHaveBeenCalled();
+ });
+
+ it('gets with multiple matching injectors', async () => {
+ const injectors = [
+ jest.fn().mockResolvedValue({ is: 'core' }),
+ jest.fn().mockReturnValue({ sync: 'injector' }),
+ jest.fn().mockResolvedValue({ is: 'merged-core' }),
+ ];
+
+ injectors.forEach(injector => legacyInternals.injectUiAppVars('core', injector));
+
+ await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(`
+ Object {
+ "is": "merged-core",
+ "sync": "injector",
+ }
+ `);
+ expect(injectors[0]).toHaveBeenCalled();
+ expect(injectors[1]).toHaveBeenCalled();
+ expect(injectors[2]).toHaveBeenCalled();
+ });
+ });
+
+ describe('getVars()', () => {
+ let uiExports: LegacyUiExports;
+ let config: LegacyConfig;
+ let server: Server;
+ let legacyInternals: LegacyInternals;
+
+ beforeEach(async () => {
+ uiExports = findLegacyPluginSpecsMock().uiExports;
+ config = configMock.create() as any;
+ server = httpServiceMock.createSetupContract().server;
+ legacyInternals = new LegacyInternals(uiExports, config, server);
+ });
+
+ it('gets: no default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => {
+ const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest());
+
+ expect(vars).toMatchInlineSnapshot(`Object {}`);
+ });
+
+ it('gets: with default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => {
+ uiExports.defaultInjectedVarProviders = [
+ varsProvider({ alpha: 'alpha' }),
+ varsProvider({ gamma: 'gamma' }),
+ varsProvider({ alpha: 'beta' }),
+ ];
+
+ const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest());
+
+ expect(vars).toMatchInlineSnapshot(`
+ Object {
+ "alpha": "beta",
+ "gamma": "gamma",
+ }
+ `);
+ });
+
+ it('gets: no default injectors, with injected vars replacers, with ui app injectors, no inject arg', async () => {
+ uiExports.injectedVarsReplacers = [
+ jest.fn(async vars => ({ ...vars, added: 'key' })),
+ jest.fn(vars => vars),
+ jest.fn(vars => ({ replaced: 'all' })),
+ jest.fn(async vars => ({ ...vars, added: 'last-key' })),
+ ];
+
+ const request = httpServerMock.createRawRequest();
+ const vars = await legacyInternals.getVars('core', request);
+
+ expect(vars).toMatchInlineSnapshot(`
+ Object {
+ "added": "last-key",
+ "replaced": "all",
+ }
+ `);
+ });
+
+ it('gets: no default injectors, no injected vars replacers, with ui app injectors, no inject arg', async () => {
+ legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' }));
+ legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' }));
+ legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' }));
+
+ const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest());
+
+ expect(vars).toMatchInlineSnapshot(`
+ Object {
+ "is": "merged-core",
+ "sync": "injector",
+ }
+ `);
+ });
+
+ it('gets: no default injectors, no injected vars replacers, no ui app injectors, with inject arg', async () => {
+ const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), {
+ injected: 'arg',
+ });
+
+ expect(vars).toMatchInlineSnapshot(`
+ Object {
+ "injected": "arg",
+ }
+ `);
+ });
+
+ it('gets: with default injectors, with injected vars replacers, with ui app injectors, with inject arg', async () => {
+ uiExports.defaultInjectedVarProviders = [
+ varsProvider({ alpha: 'alpha' }),
+ varsProvider({ gamma: 'gamma' }),
+ varsProvider({ alpha: 'beta' }),
+ ];
+ uiExports.injectedVarsReplacers = [jest.fn(async vars => ({ ...vars, gamma: 'delta' }))];
+
+ legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' }));
+ legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' }));
+ legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' }));
+
+ const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), {
+ injected: 'arg',
+ sync: 'arg',
+ });
+
+ expect(vars).toMatchInlineSnapshot(`
+ Object {
+ "alpha": "beta",
+ "gamma": "delta",
+ "injected": "arg",
+ "is": "merged-core",
+ "sync": "arg",
+ }
+ `);
+ });
+ });
+});
diff --git a/src/core/server/legacy/legacy_internals.ts b/src/core/server/legacy/legacy_internals.ts
new file mode 100644
index 0000000000000..3bf54e5f75dce
--- /dev/null
+++ b/src/core/server/legacy/legacy_internals.ts
@@ -0,0 +1,87 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Server } from 'hapi';
+
+import { LegacyRequest } from '../http';
+import { mergeVars } from './merge_vars';
+import { ILegacyInternals, LegacyVars, VarsInjector, LegacyConfig, LegacyUiExports } from './types';
+
+/**
+ * @internal
+ * @deprecated
+ */
+export class LegacyInternals implements ILegacyInternals {
+ private readonly injectors = new Map>();
+ private cachedDefaultVars?: LegacyVars;
+
+ constructor(
+ private readonly uiExports: LegacyUiExports,
+ private readonly config: LegacyConfig,
+ private readonly server: Server
+ ) {}
+
+ private get defaultVars(): LegacyVars {
+ if (this.cachedDefaultVars) {
+ return this.cachedDefaultVars;
+ }
+
+ const { defaultInjectedVarProviders = [] } = this.uiExports;
+
+ return (this.cachedDefaultVars = defaultInjectedVarProviders.reduce(
+ (vars, { fn, pluginSpec }) =>
+ mergeVars(vars, fn(this.server, pluginSpec.readConfigValue(this.config, []))),
+ {}
+ ));
+ }
+
+ private replaceVars(vars: LegacyVars, request: LegacyRequest) {
+ const { injectedVarsReplacers = [] } = this.uiExports;
+
+ return injectedVarsReplacers.reduce(
+ async (injected, replacer) => replacer(await injected, request, this.server),
+ Promise.resolve(vars)
+ );
+ }
+
+ public injectUiAppVars(id: string, injector: VarsInjector) {
+ if (!this.injectors.has(id)) {
+ this.injectors.set(id, new Set());
+ }
+
+ this.injectors.get(id)!.add(injector);
+ }
+
+ public getInjectedUiAppVars(id: string) {
+ return [...(this.injectors.get(id) || [])].reduce(
+ async (promise, injector) => ({
+ ...(await promise),
+ ...(await injector()),
+ }),
+ Promise.resolve({})
+ );
+ }
+
+ public async getVars(id: string, request: LegacyRequest, injected: LegacyVars = {}) {
+ return this.replaceVars(
+ mergeVars(this.defaultVars, await this.getInjectedUiAppVars(id), injected),
+ request
+ );
+ }
+}
diff --git a/src/core/server/legacy/legacy_service.mock.ts b/src/core/server/legacy/legacy_service.mock.ts
index ac0319cdf4eb5..495141cdcb58d 100644
--- a/src/core/server/legacy/legacy_service.mock.ts
+++ b/src/core/server/legacy/legacy_service.mock.ts
@@ -17,23 +17,33 @@
* under the License.
*/
-import { LegacyServiceDiscoverPlugins } from './legacy_service';
+import { LegacyService } from './legacy_service';
+import { LegacyServiceDiscoverPlugins, LegacyServiceSetupDeps } from './types';
-const createDiscoverMock = () => {
- const setupContract: DeeplyMockedKeys = {
- pluginSpecs: [],
- disabledPluginSpecs: [],
- uiExports: {} as any,
- settings: {},
- pluginExtendedConfig: {
- get: jest.fn(),
- has: jest.fn(),
- set: jest.fn(),
- } as any,
- };
- return setupContract;
-};
+type LegacyServiceMock = jest.Mocked & { legacyId: symbol }>;
+
+const createDiscoverPluginsMock = (): LegacyServiceDiscoverPlugins => ({
+ pluginSpecs: [],
+ uiExports: {} as any,
+ navLinks: [],
+ pluginExtendedConfig: {
+ get: jest.fn(),
+ has: jest.fn(),
+ set: jest.fn(),
+ },
+ disabledPluginSpecs: [],
+ settings: {},
+});
+const createLegacyServiceMock = (): LegacyServiceMock => ({
+ legacyId: Symbol(),
+ discoverPlugins: jest.fn().mockResolvedValue(createDiscoverPluginsMock()),
+ setup: jest.fn(),
+ start: jest.fn(),
+ stop: jest.fn(),
+});
export const legacyServiceMock = {
- createDiscover: createDiscoverMock,
+ create: createLegacyServiceMock,
+ createSetupContract: (deps: LegacyServiceSetupDeps) => createLegacyServiceMock().setup(deps),
+ createDiscoverPlugins: createDiscoverPluginsMock,
};
diff --git a/src/core/server/legacy/legacy_service.test.mocks.ts b/src/core/server/legacy/legacy_service.test.mocks.ts
index e8d4a0ed0bd4d..451a75ced7ae2 100644
--- a/src/core/server/legacy/legacy_service.test.mocks.ts
+++ b/src/core/server/legacy/legacy_service.test.mocks.ts
@@ -17,18 +17,19 @@
* under the License.
*/
-export const findLegacyPluginSpecsMock = jest
- .fn()
- .mockImplementation((settings: Record) => ({
- pluginSpecs: [],
- pluginExtendedConfig: {
- has: jest.fn(),
- get: jest.fn(() => settings),
- set: jest.fn(),
- },
- disabledPluginSpecs: [],
- uiExports: [],
- }));
+import { LegacyVars } from './types';
+
+export const findLegacyPluginSpecsMock = jest.fn().mockImplementation((settings: LegacyVars) => ({
+ pluginSpecs: [],
+ pluginExtendedConfig: {
+ has: jest.fn(),
+ get: jest.fn().mockReturnValue(settings),
+ set: jest.fn(),
+ },
+ disabledPluginSpecs: [],
+ uiExports: {},
+ navLinks: [],
+}));
jest.doMock('./plugins/find_legacy_plugin_specs.ts', () => ({
findLegacyPluginSpecs: findLegacyPluginSpecsMock,
}));
diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts
index c652bb1c94887..608392e4943f9 100644
--- a/src/core/server/legacy/legacy_service.test.ts
+++ b/src/core/server/legacy/legacy_service.test.ts
@@ -25,7 +25,7 @@ jest.mock('./config/legacy_deprecation_adapters', () => ({
import { findLegacyPluginSpecsMock } from './legacy_service.test.mocks';
import { BehaviorSubject, throwError } from 'rxjs';
-import { LegacyService, LegacyServiceSetupDeps, LegacyServiceStartDeps } from '.';
+
// @ts-ignore: implicit any for JS file
import { ClusterManager as MockClusterManager } from '../../../cli/cluster/cluster_manager';
import KbnServer from '../../../legacy/server/kbn_server';
@@ -33,7 +33,6 @@ import { Config, Env, ObjectToConfigAdapter } from '../config';
import { getEnvOptions } from '../config/__mocks__/env';
import { BasePathProxyServer } from '../http';
import { DiscoveredPlugin } from '../plugins';
-import { findLegacyPluginSpecs } from './plugins/find_legacy_plugin_specs';
import { configServiceMock } from '../config/config_service.mock';
import { loggingServiceMock } from '../logging/logging_service.mock';
@@ -42,7 +41,11 @@ import { httpServiceMock } from '../http/http_service.mock';
import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock';
import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock';
+import { setupMock as renderingServiceMock } from '../rendering/__mocks__/rendering_service';
import { uuidServiceMock } from '../uuid/uuid_service.mock';
+import { findLegacyPluginSpecs } from './plugins';
+import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types';
+import { LegacyService } from './legacy_service';
const MockKbnServer: jest.Mock = KbnServer as any;
@@ -89,6 +92,7 @@ beforeEach(() => {
browserConfigs: new Map(),
},
},
+ rendering: renderingServiceMock,
uuid: uuidSetup,
},
plugins: { 'plugin-id': 'plugin-value' },
@@ -138,7 +142,7 @@ describe('once LegacyService is set up with connection info', () => {
{ path: { autoListen: true }, server: { autoListen: true } }, // Because of the mock, path also gets the value
expect.objectContaining({ get: expect.any(Function) }),
expect.any(Object),
- { disabledPluginSpecs: [], pluginSpecs: [], uiExports: [] }
+ { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] }
);
expect(MockKbnServer.mock.calls[0][1].get()).toEqual({
path: { autoListen: true },
@@ -168,7 +172,7 @@ describe('once LegacyService is set up with connection info', () => {
{ path: { autoListen: false }, server: { autoListen: true } },
expect.objectContaining({ get: expect.any(Function) }),
expect.any(Object),
- { disabledPluginSpecs: [], pluginSpecs: [], uiExports: [] }
+ { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] }
);
expect(MockKbnServer.mock.calls[0][1].get()).toEqual({
path: { autoListen: false },
@@ -309,7 +313,7 @@ describe('once LegacyService is set up without connection info', () => {
{ path: {}, server: { autoListen: true } },
expect.objectContaining({ get: expect.any(Function) }),
expect.any(Object),
- { disabledPluginSpecs: [], pluginSpecs: [], uiExports: [] }
+ { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] }
);
expect(MockKbnServer.mock.calls[0][1].get()).toEqual({
path: {},
@@ -395,16 +399,18 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
});
});
-test('Cannot start without setup phase', async () => {
- const legacyService = new LegacyService({
- coreId,
- env,
- logger,
- configService: configService as any,
+describe('start', () => {
+ test('Cannot start without setup phase', async () => {
+ const legacyService = new LegacyService({
+ coreId,
+ env,
+ logger,
+ configService: configService as any,
+ });
+ await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Legacy service is not setup yet."`
+ );
});
- await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
- `"Legacy service is not setup yet."`
- );
});
describe('#discoverPlugins()', () => {
@@ -438,7 +444,8 @@ describe('#discoverPlugins()', () => {
],
pluginExtendedConfig: settings,
disabledPluginSpecs: [],
- uiExports: [],
+ uiExports: {},
+ navLinks: [],
}) as any
);
@@ -469,15 +476,16 @@ test('Sets the server.uuid property on the legacy configuration', async () => {
const configSetMock = jest.fn();
- findLegacyPluginSpecsMock.mockImplementation((settings: Record) => ({
+ findLegacyPluginSpecsMock.mockImplementation((settings: LegacyVars) => ({
pluginSpecs: [],
pluginExtendedConfig: {
has: jest.fn(),
- get: jest.fn(() => settings),
+ get: jest.fn().mockReturnValue(settings),
set: configSetMock,
},
disabledPluginSpecs: [],
- uiExports: [],
+ uiExports: {},
+ navLinks: [],
}));
await legacyService.discoverPlugins();
diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts
index 2e8a467eff995..2ed87f4c6d488 100644
--- a/src/core/server/legacy/legacy_service.ts
+++ b/src/core/server/legacy/legacy_service.ts
@@ -19,24 +19,30 @@
import { combineLatest, ConnectableObservable, EMPTY, Observable, Subscription } from 'rxjs';
import { first, map, publishReplay, tap } from 'rxjs/operators';
+
import { CoreService } from '../../types';
-import { CoreSetup, CoreStart } from '../';
-import { InternalCoreSetup, InternalCoreStart } from '../internal_types';
-import { SavedObjectsLegacyUiExports } from '../types';
import { Config, ConfigDeprecationProvider } from '../config';
import { CoreContext } from '../core_context';
import { CspConfigType, config as cspConfig } from '../csp';
import { DevConfig, DevConfigType, config as devConfig } from '../dev';
import { BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig } from '../http';
import { Logger } from '../logging';
-import { PluginsServiceSetup, PluginsServiceStart } from '../plugins';
-import { findLegacyPluginSpecs } from './plugins';
-import { LegacyPluginSpec } from './plugins/find_legacy_plugin_specs';
import { PathConfigType } from '../path';
-import { LegacyConfig, convertLegacyDeprecationProvider } from './config';
+import { findLegacyPluginSpecs } from './plugins';
+import { convertLegacyDeprecationProvider } from './config';
+import {
+ LegacyServiceSetupDeps,
+ LegacyServiceStartDeps,
+ LegacyPlugins,
+ LegacyServiceDiscoverPlugins,
+ LegacyConfig,
+ LegacyVars,
+} from './types';
+import { LegacyInternals } from './legacy_internals';
+import { CoreSetup, CoreStart } from '..';
interface LegacyKbnServer {
- applyLoggingConfiguration: (settings: Readonly>) => void;
+ applyLoggingConfiguration: (settings: Readonly) => void;
listen: () => Promise;
ready: () => Promise;
close: () => Promise;
@@ -53,43 +59,14 @@ function getLegacyRawConfig(config: Config, pathConfig: PathConfigType) {
return {
...rawConfig,
- path: pathConfig, // We rely heavily in the default value of 'path.data' in the legacy world and, since it has been moved to NP, it won't show up in RawConfig
- };
-}
-
-/**
- * @public
- * @deprecated
- */
-export interface LegacyServiceSetupDeps {
- core: InternalCoreSetup & {
- plugins: PluginsServiceSetup;
- };
- plugins: Record;
-}
-
-/**
- * @public
- * @deprecated
- */
-export interface LegacyServiceStartDeps {
- core: InternalCoreStart & {
- plugins: PluginsServiceStart;
+ // We rely heavily in the default value of 'path.data' in the legacy world and,
+ // since it has been moved to NP, it won't show up in RawConfig.
+ path: pathConfig,
};
- plugins: Record;
}
/** @internal */
-export interface LegacyServiceDiscoverPlugins {
- pluginSpecs: LegacyPluginSpec[];
- disabledPluginSpecs: LegacyPluginSpec[];
- uiExports: SavedObjectsLegacyUiExports;
- pluginExtendedConfig: LegacyConfig;
- settings: Record;
-}
-
-/** @internal */
-export type ILegacyService = Pick;
+export type ILegacyService = PublicMethodsOf;
/** @internal */
export class LegacyService implements CoreService {
@@ -101,16 +78,10 @@ export class LegacyService implements CoreService {
private kbnServer?: LegacyKbnServer;
private configSubscription?: Subscription;
private setupDeps?: LegacyServiceSetupDeps;
- private update$: ConnectableObservable<[Config, PathConfigType]> | undefined;
- private legacyRawConfig: LegacyConfig | undefined;
- private legacyPlugins:
- | {
- pluginSpecs: LegacyPluginSpec[];
- disabledPluginSpecs: LegacyPluginSpec[];
- uiExports: SavedObjectsLegacyUiExports;
- }
- | undefined;
- private settings: Record | undefined;
+ private update$?: ConnectableObservable<[Config, PathConfigType]>;
+ private legacyRawConfig?: LegacyConfig;
+ private legacyPlugins?: LegacyPlugins;
+ private settings?: LegacyVars;
constructor(private readonly coreContext: CoreContext) {
const { logger, configService, env } = coreContext;
@@ -153,12 +124,14 @@ export class LegacyService implements CoreService {
pluginExtendedConfig,
disabledPluginSpecs,
uiExports,
+ navLinks,
} = await findLegacyPluginSpecs(this.settings, this.coreContext.logger);
this.legacyPlugins = {
pluginSpecs,
disabledPluginSpecs,
uiExports,
+ navLinks,
};
const deprecationProviders = await pluginSpecs
@@ -188,6 +161,7 @@ export class LegacyService implements CoreService {
pluginSpecs,
disabledPluginSpecs,
uiExports,
+ navLinks,
pluginExtendedConfig,
settings: this.settings,
};
@@ -195,35 +169,37 @@ export class LegacyService implements CoreService {
public async setup(setupDeps: LegacyServiceSetupDeps) {
this.log.debug('setting up legacy service');
- if (!this.legacyRawConfig || !this.legacyPlugins || !this.settings) {
+
+ if (!this.legacyPlugins) {
throw new Error(
'Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()'
);
}
- // propagate the instance uuid to the legacy config, as it was the legacy way to access it.
- this.legacyRawConfig.set('server.uuid', setupDeps.core.uuid.getInstanceUuid());
+ // propagate the instance uuid to the legacy config, as it was the legacy way to access it.
+ this.legacyRawConfig!.set('server.uuid', setupDeps.core.uuid.getInstanceUuid());
this.setupDeps = setupDeps;
}
public async start(startDeps: LegacyServiceStartDeps) {
const { setupDeps } = this;
- if (!setupDeps || !this.legacyRawConfig || !this.legacyPlugins || !this.settings) {
+
+ if (!setupDeps || !this.legacyPlugins) {
throw new Error('Legacy service is not setup yet.');
}
+
this.log.debug('starting legacy service');
// Receive initial config and create kbnServer/ClusterManager.
-
if (this.coreContext.env.isDevClusterMaster) {
- await this.createClusterManager(this.legacyRawConfig);
+ await this.createClusterManager(this.legacyRawConfig!);
} else {
this.kbnServer = await this.createKbnServer(
- this.settings,
- this.legacyRawConfig,
+ this.settings!,
+ this.legacyRawConfig!,
setupDeps,
startDeps,
- this.legacyPlugins
+ this.legacyPlugins!
);
}
}
@@ -263,15 +239,11 @@ export class LegacyService implements CoreService {
}
private async createKbnServer(
- settings: Record,
+ settings: LegacyVars,
config: LegacyConfig,
setupDeps: LegacyServiceSetupDeps,
startDeps: LegacyServiceStartDeps,
- legacyPlugins: {
- pluginSpecs: LegacyPluginSpec[];
- disabledPluginSpecs: LegacyPluginSpec[];
- uiExports: SavedObjectsLegacyUiExports;
- }
+ legacyPlugins: LegacyPlugins
) {
const coreSetup: CoreSetup = {
capabilities: setupDeps.core.capabilities,
@@ -338,8 +310,10 @@ export class LegacyService implements CoreService {
kibanaMigrator: startDeps.core.savedObjects.migrator,
uiPlugins: setupDeps.core.plugins.uiPlugins,
elasticsearch: setupDeps.core.elasticsearch,
+ rendering: setupDeps.core.rendering,
uiSettings: setupDeps.core.uiSettings,
savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider,
+ legacy: new LegacyInternals(legacyPlugins.uiExports, config, setupDeps.core.http.server),
},
logger: this.coreContext.logger,
},
diff --git a/src/core/server/legacy/logging/appenders/legacy_appender.ts b/src/core/server/legacy/logging/appenders/legacy_appender.ts
index 011dfae8a5cef..6d82d929e7daa 100644
--- a/src/core/server/legacy/logging/appenders/legacy_appender.ts
+++ b/src/core/server/legacy/logging/appenders/legacy_appender.ts
@@ -21,6 +21,7 @@ import { schema } from '@kbn/config-schema';
import { DisposableAppender } from '../../../logging/appenders/appenders';
import { LogRecord } from '../../../logging/log_record';
import { LegacyLoggingServer } from '../legacy_logging_server';
+import { LegacyVars } from '../../types';
/**
* Simple appender that just forwards `LogRecord` to the legacy KbnServer log.
@@ -34,7 +35,7 @@ export class LegacyAppender implements DisposableAppender {
private readonly loggingServer: LegacyLoggingServer;
- constructor(legacyLoggingConfig: Readonly>) {
+ constructor(legacyLoggingConfig: Readonly) {
this.loggingServer = new LegacyLoggingServer(legacyLoggingConfig);
}
diff --git a/src/core/server/legacy/logging/legacy_logging_server.ts b/src/core/server/legacy/logging/legacy_logging_server.ts
index 57706bcac2232..85a8686b4eded 100644
--- a/src/core/server/legacy/logging/legacy_logging_server.ts
+++ b/src/core/server/legacy/logging/legacy_logging_server.ts
@@ -25,9 +25,10 @@ import { Config } from '../../../../legacy/server/config';
import { setupLogging } from '../../../../legacy/server/logging';
import { LogLevel } from '../../logging/log_level';
import { LogRecord } from '../../logging/log_record';
+import { LegacyVars } from '../../types';
export const metadataSymbol = Symbol('log message with metadata');
-export function attachMetaData(message: string, metadata: Record = {}) {
+export function attachMetaData(message: string, metadata: LegacyVars = {}) {
return {
[metadataSymbol]: {
message,
@@ -50,7 +51,7 @@ interface PluginRegisterParams {
options: PluginRegisterParams['options']
) => Promise;
};
- options: Record;
+ options: LegacyVars;
}
/**
@@ -84,7 +85,7 @@ export class LegacyLoggingServer {
private onPostStopCallback?: () => void;
- constructor(legacyLoggingConfig: Readonly>) {
+ constructor(legacyLoggingConfig: Readonly) {
// We set `ops.interval` to max allowed number and `ops` filter to value
// that doesn't exist to avoid logging of ops at all, if turned on it will be
// logged by the "legacy" Kibana.
diff --git a/src/legacy/ui/ui_render/lib/merge_variables.test.ts b/src/core/server/legacy/merge_vars.test.ts
similarity index 58%
rename from src/legacy/ui/ui_render/lib/merge_variables.test.ts
rename to src/core/server/legacy/merge_vars.test.ts
index 4d69216bc0bfd..d977ee292d039 100644
--- a/src/legacy/ui/ui_render/lib/merge_variables.test.ts
+++ b/src/core/server/legacy/merge_vars.test.ts
@@ -17,29 +17,26 @@
* under the License.
*/
-import { mergeVariables } from './merge_variables';
+import { mergeVars } from './merge_vars';
-describe('mergeVariables', () => {
+describe('mergeVars', () => {
it('merges two objects together', () => {
- const someVariables = {
- name: 'value',
- canFoo: true,
- nested: {
- anotherVariable: 'ok',
- },
- };
-
- const otherVariables = {
+ const first = {
otherName: 'value',
otherCanFoo: true,
otherNested: {
otherAnotherVariable: 'ok',
},
};
+ const second = {
+ name: 'value',
+ canFoo: true,
+ nested: {
+ anotherVariable: 'ok',
+ },
+ };
- const result = mergeVariables(someVariables, otherVariables);
-
- expect(result).toEqual({
+ expect(mergeVars(first, second)).toEqual({
name: 'value',
canFoo: true,
nested: {
@@ -54,86 +51,76 @@ describe('mergeVariables', () => {
});
it('does not mutate the source objects', () => {
- const original = {
- var1: 'original',
+ const first = {
+ var1: 'first',
};
-
- const set1 = {
- var1: 'value1',
- var2: 'value1',
+ const second = {
+ var1: 'second',
+ var2: 'second',
};
-
- const set2 = {
- var1: 'value2',
- var2: 'value2',
- var3: 'value2',
+ const third = {
+ var1: 'third',
+ var2: 'third',
+ var3: 'third',
};
-
- const set3 = {
- var1: 'value3',
- var2: 'value3',
- var3: 'value3',
- var4: 'value3',
+ const fourth = {
+ var1: 'fourth',
+ var2: 'fourth',
+ var3: 'fourth',
+ var4: 'fourth',
};
- mergeVariables(original, set1, set2, set3);
+ mergeVars(first, second, third, fourth);
- expect(original).toEqual({ var1: 'original' });
- expect(set1).toEqual({ var1: 'value1', var2: 'value1' });
- expect(set2).toEqual({ var1: 'value2', var2: 'value2', var3: 'value2' });
- expect(set3).toEqual({ var1: 'value3', var2: 'value3', var3: 'value3', var4: 'value3' });
+ expect(first).toEqual({ var1: 'first' });
+ expect(second).toEqual({ var1: 'second', var2: 'second' });
+ expect(third).toEqual({ var1: 'third', var2: 'third', var3: 'third' });
+ expect(fourth).toEqual({ var1: 'fourth', var2: 'fourth', var3: 'fourth', var4: 'fourth' });
});
- it('merges multiple objects together, preferring the leftmost values', () => {
- const original = {
- var1: 'original',
+ it('merges multiple objects together with precedence increasing from left-to-right', () => {
+ const first = {
+ var1: 'first',
+ var2: 'first',
+ var3: 'first',
+ var4: 'first',
};
-
- const set1 = {
- var1: 'value1',
- var2: 'value1',
+ const second = {
+ var1: 'second',
+ var2: 'second',
+ var3: 'second',
};
-
- const set2 = {
- var1: 'value2',
- var2: 'value2',
- var3: 'value2',
+ const third = {
+ var1: 'third',
+ var2: 'third',
};
-
- const set3 = {
- var1: 'value3',
- var2: 'value3',
- var3: 'value3',
- var4: 'value3',
+ const fourth = {
+ var1: 'fourth',
};
- const result = mergeVariables(original, set1, set2, set3);
-
- expect(result).toEqual({
- var1: 'original',
- var2: 'value1',
- var3: 'value2',
- var4: 'value3',
+ expect(mergeVars(first, second, third, fourth)).toEqual({
+ var1: 'fourth',
+ var2: 'third',
+ var3: 'second',
+ var4: 'first',
});
});
- it('retains the original variable value if a duplicate entry is found', () => {
- const someVariables = {
- name: 'value',
- canFoo: true,
+ it('overwrites the original variable value if a duplicate entry is found', () => {
+ const first = {
nested: {
- anotherVariable: 'ok',
+ otherAnotherVariable: 'ok',
},
};
-
- const otherVariables = {
+ const second = {
+ name: 'value',
+ canFoo: true,
nested: {
- otherAnotherVariable: 'ok',
+ anotherVariable: 'ok',
},
};
- const result = mergeVariables(someVariables, otherVariables);
- expect(result).toEqual({
+ expect(mergeVars(first, second)).toEqual({
name: 'value',
canFoo: true,
nested: {
@@ -143,55 +130,61 @@ describe('mergeVariables', () => {
});
it('combines entries within "uiCapabilities"', () => {
- const someVariables = {
- name: 'value',
- canFoo: true,
+ const first = {
uiCapabilities: {
firstCapability: 'ok',
+ sharedCapability: 'shared',
},
};
-
- const otherVariables = {
+ const second = {
+ name: 'value',
+ canFoo: true,
uiCapabilities: {
secondCapability: 'ok',
},
};
+ const third = {
+ name: 'value',
+ canFoo: true,
+ uiCapabilities: {
+ thirdCapability: 'ok',
+ sharedCapability: 'blocked',
+ },
+ };
- const result = mergeVariables(someVariables, otherVariables);
-
- expect(result).toEqual({
+ expect(mergeVars(first, second, third)).toEqual({
name: 'value',
canFoo: true,
uiCapabilities: {
firstCapability: 'ok',
secondCapability: 'ok',
+ thirdCapability: 'ok',
+ sharedCapability: 'blocked',
},
});
});
it('does not deeply combine entries within "uiCapabilities"', () => {
- const someVariables = {
- name: 'value',
- canFoo: true,
+ const first = {
uiCapabilities: {
firstCapability: 'ok',
nestedCapability: {
- nestedProp: 'nestedValue',
+ otherNestedProp: 'otherNestedValue',
},
},
};
-
- const otherVariables = {
+ const second = {
+ name: 'value',
+ canFoo: true,
uiCapabilities: {
secondCapability: 'ok',
nestedCapability: {
- otherNestedProp: 'otherNestedValue',
+ nestedProp: 'nestedValue',
},
},
};
- const result = mergeVariables(someVariables, otherVariables);
- expect(result).toEqual({
+ expect(mergeVars(first, second)).toEqual({
name: 'value',
canFoo: true,
uiCapabilities: {
diff --git a/src/legacy/ui/ui_render/lib/merge_variables.ts b/src/core/server/legacy/merge_vars.ts
similarity index 65%
rename from src/legacy/ui/ui_render/lib/merge_variables.ts
rename to src/core/server/legacy/merge_vars.ts
index 0f65c7825bdba..a1d43af2f861d 100644
--- a/src/legacy/ui/ui_render/lib/merge_variables.ts
+++ b/src/core/server/legacy/merge_vars.ts
@@ -17,23 +17,18 @@
* under the License.
*/
-const ELIGIBLE_FLAT_MERGE_KEYS = ['uiCapabilities'];
-
-export function mergeVariables(...sources: Array>) {
- const result: Record = {};
+import { LegacyVars } from './types';
- for (const source of sources) {
- Object.entries(source).forEach(([key, value]) => {
- if (ELIGIBLE_FLAT_MERGE_KEYS.includes(key)) {
- result[key] = {
- ...value,
- ...result[key],
- };
- } else if (!result.hasOwnProperty(key)) {
- result[key] = value;
- }
- });
- }
+const ELIGIBLE_FLAT_MERGE_KEYS = ['uiCapabilities'];
- return result;
+export function mergeVars(...sources: LegacyVars[]): LegacyVars {
+ return Object.assign(
+ {},
+ ...sources,
+ ...ELIGIBLE_FLAT_MERGE_KEYS.flatMap(key =>
+ sources.some(source => key in source)
+ ? [{ [key]: Object.assign({}, ...sources.map(source => source[key] || {})) }]
+ : []
+ )
+ );
}
diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts
index 0a49154801e56..d2e7a39236d0a 100644
--- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts
+++ b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts
@@ -19,25 +19,77 @@
import { Observable, merge, forkJoin } from 'rxjs';
import { toArray, tap, distinct, map } from 'rxjs/operators';
+
import {
findPluginSpecs,
defaultConfig,
// @ts-ignore
} from '../../../../legacy/plugin_discovery/find_plugin_specs.js';
-import { LoggerFactory } from '../../logging';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { collectUiExports as collectLegacyUiExports } from '../../../../legacy/ui/ui_exports/collect_ui_exports';
-import { LegacyConfig, LegacyConfigDeprecationProvider } from '../config';
-export interface LegacyPluginPack {
- getPath(): string;
+import { LoggerFactory } from '../../logging';
+import {
+ LegacyUiExports,
+ LegacyNavLink,
+ LegacyPluginSpec,
+ LegacyPluginPack,
+ LegacyConfig,
+} from '../types';
+
+const REMOVE_FROM_ARRAY: LegacyNavLink[] = [];
+
+function getUiAppsNavLinks({ uiAppSpecs = [] }: LegacyUiExports, pluginSpecs: LegacyPluginSpec[]) {
+ return uiAppSpecs.flatMap(spec => {
+ if (!spec) {
+ return REMOVE_FROM_ARRAY;
+ }
+
+ const id = spec.pluginId || spec.id;
+
+ if (!id) {
+ throw new Error('Every app must specify an id');
+ }
+
+ if (spec.pluginId && !pluginSpecs.some(plugin => plugin.getId() === spec.pluginId)) {
+ throw new Error(`Unknown plugin id "${spec.pluginId}"`);
+ }
+
+ const listed = typeof spec.listed === 'boolean' ? spec.listed : true;
+
+ if (spec.hidden || !listed) {
+ return REMOVE_FROM_ARRAY;
+ }
+
+ return {
+ id,
+ title: spec.title,
+ order: typeof spec.order === 'number' ? spec.order : 0,
+ icon: spec.icon,
+ euiIconType: spec.euiIconType,
+ url: spec.url || `/app/${id}`,
+ linkToLastSubUrl: spec.linkToLastSubUrl,
+ };
+ });
}
-export interface LegacyPluginSpec {
- getId: () => unknown;
- getExpectedKibanaVersion: () => string;
- getConfigPrefix: () => string;
- getDeprecationsProvider: () => LegacyConfigDeprecationProvider | undefined;
+function getNavLinks(uiExports: LegacyUiExports, pluginSpecs: LegacyPluginSpec[]) {
+ return (uiExports.navLinkSpecs || [])
+ .map(spec => ({
+ id: spec.id,
+ title: spec.title,
+ order: typeof spec.order === 'number' ? spec.order : 0,
+ url: spec.url,
+ subUrlBase: spec.subUrlBase || spec.url,
+ icon: spec.icon,
+ euiIconType: spec.euiIconType,
+ linkToLastSub: 'linkToLastSubUrl' in spec ? spec.linkToLastSubUrl : false,
+ hidden: 'hidden' in spec ? spec.hidden : false,
+ disabled: 'disabled' in spec ? spec.disabled : false,
+ tooltip: spec.tooltip || '',
+ }))
+ .concat(getUiAppsNavLinks(uiExports, pluginSpecs))
+ .sort((a, b) => a.order - b.order);
}
export async function findLegacyPluginSpecs(settings: unknown, loggerFactory: LoggerFactory) {
@@ -128,11 +180,14 @@ export async function findLegacyPluginSpecs(settings: unknown, loggerFactory: Lo
spec$.pipe(toArray()),
log$.pipe(toArray())
).toPromise();
+ const uiExports = collectLegacyUiExports(pluginSpecs);
+ const navLinks = getNavLinks(uiExports, pluginSpecs);
return {
disabledPluginSpecs,
pluginSpecs,
pluginExtendedConfig: configToMutate,
- uiExports: collectLegacyUiExports(pluginSpecs),
+ uiExports,
+ navLinks,
};
}
diff --git a/src/core/server/legacy/plugins/index.ts b/src/core/server/legacy/plugins/index.ts
index 7c69546f0c4de..a6d55e1da7839 100644
--- a/src/core/server/legacy/plugins/index.ts
+++ b/src/core/server/legacy/plugins/index.ts
@@ -16,4 +16,5 @@
* specific language governing permissions and limitations
* under the License.
*/
+
export { findLegacyPluginSpecs } from './find_legacy_plugin_specs';
diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts
new file mode 100644
index 0000000000000..6ec893be9b310
--- /dev/null
+++ b/src/core/server/legacy/types.ts
@@ -0,0 +1,222 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Server } from 'hapi';
+
+import { ChromeNavLink } from '../../public';
+import { LegacyRequest } from '../http';
+import { InternalCoreSetup, InternalCoreStart } from '../internal_types';
+import { PluginsServiceSetup, PluginsServiceStart } from '../plugins';
+import { RenderingServiceSetup } from '../rendering';
+import { SavedObjectsLegacyUiExports } from '../types';
+
+/**
+ * @internal
+ * @deprecated
+ */
+export type LegacyVars = Record;
+
+type LegacyCoreSetup = InternalCoreSetup & {
+ plugins: PluginsServiceSetup;
+ rendering: RenderingServiceSetup;
+};
+type LegacyCoreStart = InternalCoreStart & { plugins: PluginsServiceStart };
+
+/**
+ * New platform representation of the legacy configuration (KibanaConfig)
+ *
+ * @internal
+ * @deprecated
+ */
+export interface LegacyConfig {
+ get(key?: string): T;
+ has(key: string): boolean;
+ set(key: string, value: any): void;
+ set(config: LegacyVars): void;
+}
+
+/**
+ * Representation of a legacy configuration deprecation factory used for
+ * legacy plugin deprecations.
+ *
+ * @internal
+ * @deprecated
+ */
+export interface LegacyConfigDeprecationFactory {
+ rename(oldKey: string, newKey: string): LegacyConfigDeprecation;
+ unused(unusedKey: string): LegacyConfigDeprecation;
+}
+
+/**
+ * Representation of a legacy configuration deprecation.
+ *
+ * @internal
+ * @deprecated
+ */
+export type LegacyConfigDeprecation = (settings: LegacyVars, log: (msg: string) => void) => void;
+
+/**
+ * Representation of a legacy configuration deprecation provider.
+ *
+ * @internal
+ * @deprecated
+ */
+export type LegacyConfigDeprecationProvider = (
+ factory: LegacyConfigDeprecationFactory
+) => LegacyConfigDeprecation[] | Promise;
+
+/**
+ * @internal
+ * @deprecated
+ */
+export interface LegacyPluginPack {
+ getPath(): string;
+}
+
+/**
+ * @internal
+ * @deprecated
+ */
+export interface LegacyPluginSpec {
+ getId: () => unknown;
+ getExpectedKibanaVersion: () => string;
+ getConfigPrefix: () => string;
+ getDeprecationsProvider: () => LegacyConfigDeprecationProvider | undefined;
+}
+
+/**
+ * @internal
+ * @deprecated
+ */
+export interface VarsProvider {
+ fn: (server: Server, configValue: any) => LegacyVars;
+ pluginSpec: {
+ readConfigValue(config: any, key: string | string[]): any;
+ };
+}
+
+/**
+ * @internal
+ * @deprecated
+ */
+export type VarsInjector = () => LegacyVars;
+
+/**
+ * @internal
+ * @deprecated
+ */
+export type VarsReplacer = (
+ vars: LegacyVars,
+ request: LegacyRequest,
+ server: Server
+) => LegacyVars | Promise;
+
+/**
+ * @internal
+ * @deprecated
+ */
+export type LegacyNavLinkSpec = Record & ChromeNavLink;
+
+/**
+ * @internal
+ * @deprecated
+ */
+export type LegacyAppSpec = Pick<
+ ChromeNavLink,
+ 'title' | 'order' | 'icon' | 'euiIconType' | 'url' | 'linkToLastSubUrl' | 'hidden'
+> & { pluginId?: string; id?: string; listed?: boolean };
+
+/**
+ * @internal
+ * @deprecated
+ */
+export type LegacyNavLink = Omit & {
+ order: number;
+};
+
+/**
+ * @internal
+ * @deprecated
+ */
+export type LegacyUiExports = SavedObjectsLegacyUiExports & {
+ defaultInjectedVarProviders?: VarsProvider[];
+ injectedVarsReplacers?: VarsReplacer[];
+ navLinkSpecs?: LegacyNavLinkSpec[] | null;
+ uiAppSpecs?: Array;
+ unknown?: [{ pluginSpec: LegacyPluginSpec; type: unknown }];
+};
+
+/**
+ * @public
+ * @deprecated
+ */
+export interface LegacyServiceSetupDeps {
+ core: LegacyCoreSetup;
+ plugins: Record;
+}
+
+/**
+ * @public
+ * @deprecated
+ */
+export interface LegacyServiceStartDeps {
+ core: LegacyCoreStart;
+ plugins: Record;
+}
+
+/**
+ * @internal
+ * @deprecated
+ */
+export interface ILegacyInternals {
+ /**
+ * Inject UI app vars for a particular plugin
+ */
+ injectUiAppVars(id: string, injector: VarsInjector): void;
+
+ /**
+ * Get all the merged injected UI app vars for a particular plugin
+ */
+ getInjectedUiAppVars(id: string): Promise;
+
+ /**
+ * Get the metadata vars for a particular plugin
+ */
+ getVars(id: string, request: LegacyRequest, injected?: LegacyVars): Promise;
+}
+
+/**
+ * @internal
+ * @deprecated
+ */
+export interface LegacyPlugins {
+ disabledPluginSpecs: LegacyPluginSpec[];
+ pluginSpecs: LegacyPluginSpec[];
+ uiExports: LegacyUiExports;
+ navLinks: LegacyNavLink[];
+}
+
+/**
+ * @internal
+ * @deprecated
+ */
+export interface LegacyServiceDiscoverPlugins extends LegacyPlugins {
+ pluginExtendedConfig: LegacyConfig;
+ settings: LegacyVars;
+}
diff --git a/src/core/server/plugins/plugins_service.mock.ts b/src/core/server/plugins/plugins_service.mock.ts
index 8d3c6a8c909a2..5a52ebccbd472 100644
--- a/src/core/server/plugins/plugins_service.mock.ts
+++ b/src/core/server/plugins/plugins_service.mock.ts
@@ -17,28 +17,28 @@
* under the License.
*/
-import { PluginsService } from './plugins_service';
+import { PluginsService, PluginsServiceSetup } from './plugins_service';
-type ServiceContract = PublicMethodsOf;
-const createServiceMock = () => {
- const mocked: jest.Mocked = {
- discover: jest.fn(),
- setup: jest.fn(),
- start: jest.fn(),
- stop: jest.fn(),
- };
- mocked.setup.mockResolvedValue({
- contracts: new Map(),
- uiPlugins: {
- browserConfigs: new Map(),
- internal: new Map(),
- public: new Map(),
- },
- });
- mocked.start.mockResolvedValue({ contracts: new Map() });
- return mocked;
-};
+type PluginsServiceMock = jest.Mocked>;
+
+const createSetupContractMock = (): PluginsServiceSetup => ({
+ contracts: new Map(),
+ uiPlugins: {
+ browserConfigs: new Map(),
+ internal: new Map(),
+ public: new Map(),
+ },
+});
+const createStartContractMock = () => ({ contracts: new Map() });
+const createServiceMock = (): PluginsServiceMock => ({
+ discover: jest.fn(),
+ setup: jest.fn().mockResolvedValue(createSetupContractMock()),
+ start: jest.fn().mockResolvedValue(createStartContractMock()),
+ stop: jest.fn(),
+});
export const pluginServiceMock = {
create: createServiceMock,
+ createSetupContract: createSetupContractMock,
+ createStartContract: createStartContractMock,
};
diff --git a/src/core/server/rendering/__mocks__/params.ts b/src/core/server/rendering/__mocks__/params.ts
new file mode 100644
index 0000000000000..392b2f0c5e2a4
--- /dev/null
+++ b/src/core/server/rendering/__mocks__/params.ts
@@ -0,0 +1,35 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { mockCoreContext } from '../../core_context.mock';
+import { httpServiceMock } from '../../http/http_service.mock';
+import { pluginServiceMock } from '../../plugins/plugins_service.mock';
+import { legacyServiceMock } from '../../legacy/legacy_service.mock';
+
+const context = mockCoreContext.create();
+const http = httpServiceMock.createSetupContract();
+const plugins = pluginServiceMock.createSetupContract();
+const legacyPlugins = legacyServiceMock.createDiscoverPlugins();
+
+export const mockRenderingServiceParams = context;
+export const mockRenderingSetupDeps = {
+ http,
+ legacyPlugins,
+ plugins,
+};
diff --git a/src/core/server/rendering/__mocks__/rendering_service.ts b/src/core/server/rendering/__mocks__/rendering_service.ts
new file mode 100644
index 0000000000000..33dca7cc0d30e
--- /dev/null
+++ b/src/core/server/rendering/__mocks__/rendering_service.ts
@@ -0,0 +1,39 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { RenderingService as Service } from '../rendering_service';
+import { RenderingServiceSetup } from '../types';
+import { mockRenderingServiceParams } from './params';
+
+type IRenderingService = PublicMethodsOf;
+
+export const setupMock: jest.Mocked = {
+ render: jest.fn(),
+};
+export const mockSetup = jest.fn().mockResolvedValue(setupMock);
+export const mockStart = jest.fn();
+export const mockStop = jest.fn();
+export const mockRenderingService: jest.Mocked = {
+ setup: mockSetup,
+ start: mockStart,
+ stop: mockStop,
+};
+export const RenderingService = jest.fn(
+ () => mockRenderingService
+);
diff --git a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap
new file mode 100644
index 0000000000000..edde1dee85f4f
--- /dev/null
+++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap
@@ -0,0 +1,719 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RenderingService setup() render() renders "core" from legacy request 1`] = `
+Object {
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/mock-server-basepath/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:core",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {},
+ },
+ "version": Any,
+ },
+ "legacyMode": false,
+ "uiPlugins": Array [],
+ "vars": Object {},
+ "version": Any,
+}
+`;
+
+exports[`RenderingService setup() render() renders "core" page 1`] = `
+Object {
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/mock-server-basepath/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:core",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {},
+ },
+ "version": Any,
+ },
+ "legacyMode": false,
+ "uiPlugins": Array [],
+ "vars": Object {},
+ "version": Any,
+}
+`;
+
+exports[`RenderingService setup() render() renders "core" page driven by settings 1`] = `
+Object {
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/mock-server-basepath/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:core",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {
+ "theme:darkMode": Object {
+ "userValue": true,
+ },
+ },
+ },
+ "version": Any,
+ },
+ "legacyMode": false,
+ "uiPlugins": Array [],
+ "vars": Object {},
+ "version": Any,
+}
+`;
+
+exports[`RenderingService setup() render() renders "core" page for blank basepath 1`] = `
+Object {
+ "basePath": "",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:core",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {},
+ },
+ "version": Any,
+ },
+ "legacyMode": false,
+ "uiPlugins": Array [],
+ "vars": Object {},
+ "version": Any,
+}
+`;
+
+exports[`RenderingService setup() render() renders "core" with excluded user settings 1`] = `
+Object {
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/mock-server-basepath/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:core",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {},
+ },
+ "version": Any,
+ },
+ "legacyMode": false,
+ "uiPlugins": Array [],
+ "vars": Object {},
+ "version": Any,
+}
+`;
+
+exports[`RenderingService setup() render() renders "legacy" page 1`] = `
+Object {
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/mock-server-basepath/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:legacy",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {},
+ },
+ "version": Any,
+ },
+ "legacyMode": true,
+ "uiPlugins": Array [],
+ "vars": Object {},
+ "version": Any,
+}
+`;
+
+exports[`RenderingService setup() render() renders "legacy" page for blank basepath 1`] = `
+Object {
+ "basePath": "",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:legacy",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {},
+ },
+ "version": Any,
+ },
+ "legacyMode": true,
+ "uiPlugins": Array [],
+ "vars": Object {},
+ "version": Any,
+}
+`;
+
+exports[`RenderingService setup() render() renders "legacy" with custom vars 1`] = `
+Object {
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/mock-server-basepath/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:legacy",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {},
+ },
+ "version": Any,
+ },
+ "legacyMode": true,
+ "uiPlugins": Array [],
+ "vars": Object {
+ "fake": "__TEST_TOKEN__",
+ },
+ "version": Any,
+}
+`;
+
+exports[`RenderingService setup() render() renders "legacy" with excluded user settings 1`] = `
+Object {
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/mock-server-basepath/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:legacy",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {},
+ },
+ "version": Any,
+ },
+ "legacyMode": true,
+ "uiPlugins": Array [],
+ "vars": Object {},
+ "version": Any,
+}
+`;
+
+exports[`RenderingService setup() render() renders "legacy" with excluded user settings and custom vars 1`] = `
+Object {
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNumber": Any,
+ "csp": Object {
+ "warnLegacyBrowsers": true,
+ },
+ "env": Object {
+ "binDir": Any,
+ "cliArgs": Object {
+ "basePath": false,
+ "dev": true,
+ "open": false,
+ "optimize": false,
+ "oss": false,
+ "quiet": false,
+ "repl": false,
+ "silent": false,
+ "watch": false,
+ },
+ "configDir": Any,
+ "configs": Array [],
+ "homeDir": Any,
+ "isDevClusterMaster": false,
+ "logDir": Any,
+ "mode": Object {
+ "dev": true,
+ "name": "development",
+ "prod": false,
+ },
+ "packageInfo": Object {
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "dist": false,
+ "version": Any,
+ },
+ "pluginSearchPaths": Any,
+ "staticFilesDir": Any,
+ },
+ "i18n": Object {
+ "translationsUrl": "/mock-server-basepath/translations/en.json",
+ },
+ "legacyMetadata": Object {
+ "app": Object {},
+ "basePath": "/mock-server-basepath",
+ "branch": Any,
+ "buildNum": Any,
+ "buildSha": Any,
+ "bundleId": "app:legacy",
+ "devMode": true,
+ "nav": Array [],
+ "serverName": "http-server-test",
+ "uiSettings": Object {
+ "defaults": Object {
+ "registered": Object {
+ "name": "title",
+ },
+ },
+ "user": Object {},
+ },
+ "version": Any,
+ },
+ "legacyMode": true,
+ "uiPlugins": Array [],
+ "vars": Object {
+ "fake": "__TEST_TOKEN__",
+ },
+ "version": Any,
+}
+`;
diff --git a/src/core/server/rendering/index.ts b/src/core/server/rendering/index.ts
new file mode 100644
index 0000000000000..233f4b26a70db
--- /dev/null
+++ b/src/core/server/rendering/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { RenderingService } from './rendering_service';
+export * from './types';
diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts
new file mode 100644
index 0000000000000..63145f2b30573
--- /dev/null
+++ b/src/core/server/rendering/rendering_service.test.ts
@@ -0,0 +1,185 @@
+/*
+ * 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 { load } from 'cheerio';
+
+import { httpServerMock } from '../http/http_server.mocks';
+import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
+import { mockRenderingServiceParams, mockRenderingSetupDeps } from './__mocks__/params';
+import { RenderingServiceSetup } from './types';
+import { RenderingService } from './rendering_service';
+
+const INJECTED_METADATA = {
+ version: expect.any(String),
+ branch: expect.any(String),
+ buildNumber: expect.any(Number),
+ env: {
+ binDir: expect.any(String),
+ configDir: expect.any(String),
+ homeDir: expect.any(String),
+ logDir: expect.any(String),
+ packageInfo: {
+ branch: expect.any(String),
+ buildNum: expect.any(Number),
+ buildSha: expect.any(String),
+ version: expect.any(String),
+ },
+ pluginSearchPaths: expect.any(Array),
+ staticFilesDir: expect.any(String),
+ },
+ legacyMetadata: {
+ branch: expect.any(String),
+ buildNum: expect.any(Number),
+ buildSha: expect.any(String),
+ version: expect.any(String),
+ },
+};
+const { createKibanaRequest, createRawRequest } = httpServerMock;
+const legacyApp = { getId: () => 'legacy' };
+
+describe('RenderingService', () => {
+ let service: RenderingService;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ service = new RenderingService(mockRenderingServiceParams);
+ });
+
+ describe('setup()', () => {
+ it('creates instance of RenderingServiceSetup', async () => {
+ const rendering = await service.setup(mockRenderingSetupDeps);
+
+ expect(rendering.render).toBeInstanceOf(Function);
+ });
+
+ describe('render()', () => {
+ let uiSettings: ReturnType;
+ let render: RenderingServiceSetup['render'];
+
+ beforeEach(async () => {
+ uiSettings = uiSettingsServiceMock.createClient();
+ uiSettings.getRegistered.mockReturnValue({
+ registered: { name: 'title' },
+ });
+ render = (await service.setup(mockRenderingSetupDeps)).render;
+ });
+
+ it('renders "core" page', async () => {
+ const content = await render(createKibanaRequest(), uiSettings);
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+
+ it('renders "core" page for blank basepath', async () => {
+ mockRenderingSetupDeps.http.basePath.get.mockReturnValueOnce('');
+
+ const content = await render(createKibanaRequest(), uiSettings);
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+
+ it('renders "core" page driven by settings', async () => {
+ uiSettings.getUserProvided.mockResolvedValue({ 'theme:darkMode': { userValue: true } });
+ const content = await render(createKibanaRequest(), uiSettings);
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+
+ it('renders "core" with excluded user settings', async () => {
+ const content = await render(createKibanaRequest(), uiSettings, {
+ includeUserSettings: false,
+ });
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+
+ it('renders "core" from legacy request', async () => {
+ const content = await render(createRawRequest(), uiSettings);
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+
+ it('renders "legacy" page', async () => {
+ const content = await render(createRawRequest(), uiSettings, { app: legacyApp });
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+
+ it('renders "legacy" page for blank basepath', async () => {
+ mockRenderingSetupDeps.http.basePath.get.mockReturnValueOnce('');
+
+ const content = await render(createRawRequest(), uiSettings, { app: legacyApp });
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+
+ it('renders "legacy" with custom vars', async () => {
+ const content = await render(createRawRequest(), uiSettings, {
+ app: legacyApp,
+ vars: {
+ fake: '__TEST_TOKEN__',
+ },
+ });
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+
+ it('renders "legacy" with excluded user settings', async () => {
+ const content = await render(createRawRequest(), uiSettings, {
+ app: legacyApp,
+ includeUserSettings: false,
+ });
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+
+ it('renders "legacy" with excluded user settings and custom vars', async () => {
+ const content = await render(createRawRequest(), uiSettings, {
+ app: legacyApp,
+ includeUserSettings: false,
+ vars: {
+ fake: '__TEST_TOKEN__',
+ },
+ });
+ const dom = load(content);
+ const data = JSON.parse(dom('kbn-injected-metadata').attr('data'));
+
+ expect(data).toMatchSnapshot(INJECTED_METADATA);
+ });
+ });
+ });
+});
diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx
new file mode 100644
index 0000000000000..41810c6a10655
--- /dev/null
+++ b/src/core/server/rendering/rendering_service.tsx
@@ -0,0 +1,120 @@
+/*
+ * 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 React from 'react';
+import { renderToStaticMarkup } from 'react-dom/server';
+import { take } from 'rxjs/operators';
+
+import { i18n } from '@kbn/i18n';
+
+import { CoreService } from '../../types';
+import { CoreContext } from '../core_context';
+import { Template } from './views';
+import {
+ RenderingSetupDeps,
+ RenderingServiceSetup,
+ RenderingMetadata,
+ LegacyRenderOptions,
+} from './types';
+
+/** @internal */
+export class RenderingService implements CoreService {
+ constructor(private readonly coreContext: CoreContext) {}
+
+ public async setup({
+ http,
+ legacyPlugins,
+ plugins,
+ }: RenderingSetupDeps): Promise {
+ async function getUiConfig(pluginId: string) {
+ const browserConfig = plugins.uiPlugins.browserConfigs.get(pluginId);
+
+ return ((await browserConfig?.pipe(take(1)).toPromise()) ?? {}) as Record;
+ }
+
+ return {
+ render: async (
+ request,
+ uiSettings,
+ {
+ app = { getId: () => 'core' },
+ includeUserSettings = true,
+ vars = {},
+ }: LegacyRenderOptions = {}
+ ) => {
+ const { env } = this.coreContext;
+ const basePath = http.basePath.get(request);
+ const settings = {
+ defaults: uiSettings.getRegistered(),
+ user: includeUserSettings ? await uiSettings.getUserProvided() : {},
+ };
+ const appId = app.getId();
+ const metadata: RenderingMetadata = {
+ strictCsp: http.csp.strict,
+ uiPublicUrl: `${basePath}/ui`,
+ bootstrapScriptUrl: `${basePath}/bundles/app/${appId}/bootstrap.js`,
+ i18n: i18n.translate,
+ locale: i18n.getLocale(),
+ darkMode: settings.user?.['theme:darkMode']?.userValue
+ ? Boolean(settings.user['theme:darkMode'].userValue)
+ : false,
+ injectedMetadata: {
+ version: env.packageInfo.version,
+ buildNumber: env.packageInfo.buildNum,
+ branch: env.packageInfo.branch,
+ basePath,
+ env,
+ legacyMode: appId !== 'core',
+ i18n: {
+ translationsUrl: `${basePath}/translations/${i18n.getLocale()}.json`,
+ },
+ csp: { warnLegacyBrowsers: http.csp.warnLegacyBrowsers },
+ vars,
+ uiPlugins: await Promise.all(
+ [...plugins.uiPlugins.public].map(async ([id, plugin]) => ({
+ id,
+ plugin,
+ config: await getUiConfig(id),
+ }))
+ ),
+ legacyMetadata: {
+ app,
+ bundleId: `app:${appId}`,
+ nav: legacyPlugins.navLinks,
+ version: env.packageInfo.version,
+ branch: env.packageInfo.branch,
+ buildNum: env.packageInfo.buildNum,
+ buildSha: env.packageInfo.buildSha,
+ serverName: http.server.name,
+ devMode: env.mode.dev,
+ basePath,
+ uiSettings: settings,
+ },
+ },
+ };
+
+ return `${renderToStaticMarkup()}`;
+ },
+ };
+ }
+
+ public async start() {}
+
+ public async stop() {}
+}
diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts
new file mode 100644
index 0000000000000..31b326bab6c78
--- /dev/null
+++ b/src/core/server/rendering/types.ts
@@ -0,0 +1,145 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+import { Env } from '../config';
+import { ICspConfig } from '../csp';
+import { InternalHttpServiceSetup, KibanaRequest, LegacyRequest } from '../http';
+import { LegacyNavLink, LegacyServiceDiscoverPlugins } from '../legacy';
+import { PluginsServiceSetup, DiscoveredPlugin } from '../plugins';
+import { IUiSettingsClient, UserProvidedValues } from '../ui_settings';
+
+/** @internal */
+export interface RenderingMetadata {
+ strictCsp: ICspConfig['strict'];
+ uiPublicUrl: string;
+ bootstrapScriptUrl: string;
+ i18n: typeof i18n.translate;
+ locale: string;
+ darkMode: boolean;
+ injectedMetadata: {
+ version: string;
+ buildNumber: number;
+ branch: string;
+ basePath: string;
+ env: Env;
+ legacyMode: boolean;
+ i18n: {
+ translationsUrl: string;
+ };
+ csp: Pick;
+ vars: Record;
+ uiPlugins: Array<{
+ id: string;
+ plugin: DiscoveredPlugin;
+ config?: Record