diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md index a4fa3f17d0d94..1ce18834f5319 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md @@ -9,5 +9,5 @@ Search for objects Signature: ```typescript -find: (options: Pick) => Promise>; +find: (options: Pick) => Promise>; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md index 3c4e33db4af91..6033c667c1866 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md @@ -20,7 +20,7 @@ export declare class SavedObjectsClient | [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | (objects?: {
id: string;
type: string;
}[]) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>> | Returns an array of objects by id | | [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>> | Persists an object | | [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | (type: string, id: string) => Promise<{}> | Deletes an object | -| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "perPage" | "sortField" | "fields" | "searchFields" | "hasReference" | "defaultSearchOperator">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | +| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | | [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>> | Fetches a single object | ## Methods diff --git a/docs/development/core/server/kibana-plugin-server.basepath.get.md b/docs/development/core/server/kibana-plugin-server.basepath.get.md index 3f7895dd72799..6ef7022f10e62 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.get.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.get.md @@ -1,13 +1,13 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [BasePath](./kibana-plugin-server.basepath.md) > [get](./kibana-plugin-server.basepath.get.md) - -## BasePath.get property - -returns `basePath` value, specific for an incoming request. - -Signature: - -```typescript -(request: KibanaRequest | LegacyRequest) => string; -``` + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [BasePath](./kibana-plugin-server.basepath.md) > [get](./kibana-plugin-server.basepath.get.md) + +## BasePath.get property + +returns `basePath` value, specific for an incoming request. + +Signature: + +```typescript +get: (request: KibanaRequest | LegacyRequest) => string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.basepath.set.md b/docs/development/core/server/kibana-plugin-server.basepath.set.md index 633765389e649..56a7f644d34cc 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.set.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.set.md @@ -1,13 +1,13 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [BasePath](./kibana-plugin-server.basepath.md) > [set](./kibana-plugin-server.basepath.set.md) - -## BasePath.set property - -sets `basePath` value, specific for an incoming request. - -Signature: - -```typescript -(request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; -``` + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [BasePath](./kibana-plugin-server.basepath.md) > [set](./kibana-plugin-server.basepath.set.md) + +## BasePath.set property + +sets `basePath` value, specific for an incoming request. + +Signature: + +```typescript +set: (request: KibanaRequest | 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(