Skip to content

Commit

Permalink
Expose whitelisted config values to client-side plugin (#50641)
Browse files Browse the repository at this point in the history
* introduce PluginConfigDescriptor type

* inject client plugin configs in injectedMetadata

* expose client config in PluginInitializerContext

* add example implementation in testbed

* update generated doc

* only generates ui config entry for plugins exposing properties to client

* separate plugin configs from plugins

* restructure plugin services tests

* fix test/mocks due to plugin configs api changes

* add unit tests

* update migration guide

* update tsdoc

* fix typecheck

* use sync getter for config on client side instead of observable

* change type of exposeToBrowser prop

* updates generated doc

* fix doc and address nits
  • Loading branch information
pgayvallet committed Nov 21, 2019
1 parent 8b5e03e commit 1bef133
Show file tree
Hide file tree
Showing 35 changed files with 1,078 additions and 610 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) &gt; [config](./kibana-plugin-public.plugininitializercontext.config.md)

## PluginInitializerContext.config property

<b>Signature:</b>

```typescript
readonly config: {
get: <T extends object = ConfigSchema>() => T;
};
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ The available core services passed to a `PluginInitializer`
<b>Signature:</b>

```typescript
export interface PluginInitializerContext
export interface PluginInitializerContext<ConfigSchema extends object = object>
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [config](./kibana-plugin-public.plugininitializercontext.config.md) | <code>{</code><br/><code> get: &lt;T extends object = ConfigSchema&gt;() =&gt; T;</code><br/><code> }</code> | |
| [env](./kibana-plugin-public.plugininitializercontext.env.md) | <code>{</code><br/><code> mode: Readonly&lt;EnvironmentMode&gt;;</code><br/><code> packageInfo: Readonly&lt;PackageInfo&gt;;</code><br/><code> }</code> | |
| [opaqueId](./kibana-plugin-public.plugininitializercontext.opaqueid.md) | <code>PluginOpaqueId</code> | A symbol used to identify this plugin in the system. Needed when registering handlers or context providers. |

2 changes: 2 additions & 0 deletions docs/development/core/server/kibana-plugin-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
| [PackageInfo](./kibana-plugin-server.packageinfo.md) | |
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration schema and capabilities. |
| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. |
| [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) | |
Expand Down Expand Up @@ -156,6 +157,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation |
| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md)<!-- -->. |
| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md)<!-- -->. |
| [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. |
| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>server</code> directory should conform to this interface. |
| [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. |
| [PluginOpaqueId](./kibana-plugin-server.pluginopaqueid.md) | |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) &gt; [exposeToBrowser](./kibana-plugin-server.pluginconfigdescriptor.exposetobrowser.md)

## PluginConfigDescriptor.exposeToBrowser property

List of configuration properties that will be available on the client-side plugin.

<b>Signature:</b>

```typescript
exposeToBrowser?: {
[P in keyof T]?: boolean;
};
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md)

## PluginConfigDescriptor interface

Describes a plugin configuration schema and capabilities.

<b>Signature:</b>

```typescript
export interface PluginConfigDescriptor<T = any>
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [exposeToBrowser](./kibana-plugin-server.pluginconfigdescriptor.exposetobrowser.md) | <code>{</code><br/><code> [P in keyof T]?: boolean;</code><br/><code> }</code> | List of configuration properties that will be available on the client-side plugin. |
| [schema](./kibana-plugin-server.pluginconfigdescriptor.schema.md) | <code>PluginConfigSchema&lt;T&gt;</code> | Schema to use to validate the plugin configuration.[PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) |

## Example


```typescript
// my_plugin/server/index.ts
import { schema, TypeOf } from '@kbn/config-schema';
import { PluginConfigDescriptor } from 'kibana/server';
const configSchema = schema.object({
secret: schema.string({ defaultValue: 'Only on server' }),
uiProp: schema.string({ defaultValue: 'Accessible from client' }),
});
type ConfigType = TypeOf<typeof configSchema>;
export const config: PluginConfigDescriptor<ConfigType> = {
exposeToBrowser: {
uiProp: true,
},
schema: configSchema,
};
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) &gt; [schema](./kibana-plugin-server.pluginconfigdescriptor.schema.md)

## PluginConfigDescriptor.schema property

Schema to use to validate the plugin configuration.

[PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md)

<b>Signature:</b>

```typescript
schema: PluginConfigSchema<T>;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md)

## PluginConfigSchema type

Dedicated type for plugin configuration schema.

<b>Signature:</b>

```typescript
export declare type PluginConfigSchema<T> = Type<T>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export interface PluginsServiceSetup
| Property | Type | Description |
| --- | --- | --- |
| [contracts](./kibana-plugin-server.pluginsservicesetup.contracts.md) | <code>Map&lt;PluginName, unknown&gt;</code> | |
| [uiPluginConfigs](./kibana-plugin-server.pluginsservicesetup.uipluginconfigs.md) | <code>Map&lt;PluginName, Observable&lt;unknown&gt;&gt;</code> | |
| [uiPlugins](./kibana-plugin-server.pluginsservicesetup.uiplugins.md) | <code>{</code><br/><code> public: Map&lt;PluginName, DiscoveredPlugin&gt;;</code><br/><code> internal: Map&lt;PluginName, DiscoveredPluginInternal&gt;;</code><br/><code> }</code> | |

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) &gt; [uiPluginConfigs](./kibana-plugin-server.pluginsservicesetup.uipluginconfigs.md)

## PluginsServiceSetup.uiPluginConfigs property

<b>Signature:</b>

```typescript
uiPluginConfigs: Map<PluginName, Observable<unknown>>;
```
38 changes: 38 additions & 0 deletions src/core/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,7 @@ import { npStart: { core } } from 'ui/new_platform';
| `ui/routes` | -- | There is no global routing mechanism. Each app [configures its own routing](/rfcs/text/0004_application_service_mounting.md#complete-example). |
| `ui/saved_objects` | [`core.savedObjects`](/docs/development/core/public/kibana-plugin-public.savedobjectsstart.md) | Client API is the same |
| `ui/doc_title` | [`core.chrome.docTitle`](/docs/development/core/public/kibana-plugin-public.chromedoctitle.md) | |
| `uiExports/injectedVars` | [Configure plugin](#configure-plugin) and [`PluginConfigDescriptor.exposeToBrowser`](/docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.exposetobrowser.md) | Can only be used to expose configuration properties |

_See also: [Public's CoreStart API Docs](/docs/development/core/public/kibana-plugin-public.corestart.md)_

Expand Down Expand Up @@ -1282,6 +1283,43 @@ class MyPlugin {
}
```
If your plugin also have a client-side part, you can also expose configuration properties to it using a whitelisting mechanism with the configuration `exposeToBrowser` property.
```typescript
// my_plugin/server/index.ts
import { schema, TypeOf } from '@kbn/config-schema';
import { PluginConfigDescriptor } from 'kibana/server';

const configSchema = schema.object({
secret: schema.string({ defaultValue: 'Only on server' }),
uiProp: schema.string({ defaultValue: 'Accessible from client' }),
});

type ConfigType = TypeOf<typeof configSchema>;

export const config: PluginConfigDescriptor<ConfigType> = {
exposeToBrowser: {
uiProp: true,
},
schema: configSchema,
};
```
Configuration containing only the exposed properties will be then available on the client-side using the plugin's `initializerContext`:
```typescript
// my_plugin/public/index.ts
interface ClientConfigType {
uiProp: string;
}

export class Plugin implements Plugin<PluginSetup, PluginStart> {
constructor(private readonly initializerContext: PluginInitializerContext) {}

public async setup(core: CoreSetup, deps: {}) {
const config = this.initializerContext.config.get<ClientConfigType>();
// ...
}
```
### Mock new platform services in tests
#### Writing mocks for your plugin
Expand Down
1 change: 1 addition & 0 deletions src/core/public/injected_metadata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ export {
InjectedMetadataParams,
InjectedMetadataSetup,
InjectedMetadataStart,
InjectedPluginMetadata,
LegacyNavLink,
} from './injected_metadata_service';
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ describe('setup.getPlugins()', () => {
const injectedMetadata = new InjectedMetadataService({
injectedMetadata: {
uiPlugins: [
{ id: 'plugin-1', plugin: {} },
{ id: 'plugin-1', plugin: {}, config: { clientProp: 'clientValue' } },
{ id: 'plugin-2', plugin: {} },
],
},
} as any);

const plugins = injectedMetadata.setup().getPlugins();
expect(plugins).toEqual([
{ id: 'plugin-1', plugin: {} },
{ id: 'plugin-1', plugin: {}, config: { clientProp: 'clientValue' } },
{ id: 'plugin-2', plugin: {} },
]);
});
Expand Down
18 changes: 10 additions & 8 deletions src/core/public/injected_metadata/injected_metadata_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ export interface LegacyNavLink {
euiIconType?: string;
}

export interface InjectedPluginMetadata {
id: PluginName;
plugin: DiscoveredPlugin;
config?: {
[key: string]: unknown;
};
}

/** @internal */
export interface InjectedMetadataParams {
injectedMetadata: {
Expand All @@ -55,10 +63,7 @@ export interface InjectedMetadataParams {
mode: Readonly<EnvironmentMode>;
packageInfo: Readonly<PackageInfo>;
};
uiPlugins: Array<{
id: PluginName;
plugin: DiscoveredPlugin;
}>;
uiPlugins: InjectedPluginMetadata[];
capabilities: Capabilities;
legacyMode: boolean;
legacyMetadata: {
Expand Down Expand Up @@ -165,10 +170,7 @@ export interface InjectedMetadataSetup {
/**
* An array of frontend plugins in topological order.
*/
getPlugins: () => Array<{
id: string;
plugin: DiscoveredPlugin;
}>;
getPlugins: () => InjectedPluginMetadata[];
/** Indicates whether or not we are rendering a known legacy app. */
getLegacyMode: () => boolean;
getLegacyMetadata: () => {
Expand Down
3 changes: 3 additions & 0 deletions src/core/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ function pluginInitializerContextMock() {
dist: false,
},
},
config: {
get: <T>() => ({} as T),
},
};

return mock;
Expand Down
20 changes: 16 additions & 4 deletions src/core/public/plugins/plugin_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/

import { omit } from 'lodash';

import { DiscoveredPlugin } from '../../server';
import { PluginOpaqueId, PackageInfo, EnvironmentMode } from '../../server/types';
import { CoreContext } from '../core_system';
Expand All @@ -31,7 +30,7 @@ import { CoreSetup, CoreStart } from '../';
*
* @public
*/
export interface PluginInitializerContext {
export interface PluginInitializerContext<ConfigSchema extends object = object> {
/**
* A symbol used to identify this plugin in the system. Needed when registering handlers or context providers.
*/
Expand All @@ -40,24 +39,37 @@ export interface PluginInitializerContext {
mode: Readonly<EnvironmentMode>;
packageInfo: Readonly<PackageInfo>;
};
readonly config: {
get: <T extends object = ConfigSchema>() => T;
};
}

/**
* Provides a plugin-specific context passed to the plugin's construtor. This is currently
* empty but should provide static services in the future, such as config and logging.
*
* @param coreContext
* @param pluginManinfest
* @param opaqueId
* @param pluginManifest
* @param pluginConfig
* @internal
*/
export function createPluginInitializerContext(
coreContext: CoreContext,
opaqueId: PluginOpaqueId,
pluginManifest: DiscoveredPlugin
pluginManifest: DiscoveredPlugin,
pluginConfig: {
[key: string]: unknown;
}
): PluginInitializerContext {
return {
opaqueId,
env: coreContext.env,
config: {
get<T>() {
return (pluginConfig as unknown) as T;
},
},
};
}

Expand Down
Loading

0 comments on commit 1bef133

Please sign in to comment.