Skip to content

Commit

Permalink
Allow nested declaration for exposeToBrowser (#128864)
Browse files Browse the repository at this point in the history
* Allow nested declaration for `exposeToBrowser`

* update generated doc

* add utest
  • Loading branch information
pgayvallet authored Mar 30, 2022
1 parent 3f4aa49 commit dd0a190
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [ExposedToBrowserDescriptor](./kibana-plugin-core-server.exposedtobrowserdescriptor.md)

## ExposedToBrowserDescriptor type

Type defining the list of configuration properties that will be exposed on the client-side Object properties can either be fully exposed

<b>Signature:</b>

```typescript
export declare type ExposedToBrowserDescriptor<T> = {
[Key in keyof T]?: T[Key] extends Maybe<any[]> ? boolean : T[Key] extends Maybe<object> ? // can be nested for objects
ExposedToBrowserDescriptor<T[Key]> | boolean : boolean;
};
```
1 change: 1 addition & 0 deletions docs/development/core/server/kibana-plugin-core-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [ElasticsearchClient](./kibana-plugin-core-server.elasticsearchclient.md) | Client used to query the elasticsearch cluster. |
| [ElasticsearchClientConfig](./kibana-plugin-core-server.elasticsearchclientconfig.md) | Configuration options to be used to create a [cluster client](./kibana-plugin-core-server.iclusterclient.md) using the [createClient API](./kibana-plugin-core-server.elasticsearchservicestart.createclient.md) |
| [ExecutionContextStart](./kibana-plugin-core-server.executioncontextstart.md) | |
| [ExposedToBrowserDescriptor](./kibana-plugin-core-server.exposedtobrowserdescriptor.md) | Type defining the list of configuration properties that will be exposed on the client-side Object properties can either be fully exposed |
| [GetAuthHeaders](./kibana-plugin-core-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. |
| [GetAuthState](./kibana-plugin-core-server.getauthstate.md) | Gets authentication state for a request. Returned by <code>auth</code> interceptor. |
| [HandlerContextType](./kibana-plugin-core-server.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-core-server.handlerfunction.md) to represent the type of the context. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,5 @@ List of configuration properties that will be available on the client-side plugi
<b>Signature:</b>

```typescript
exposeToBrowser?: {
[P in keyof T]?: boolean;
};
exposeToBrowser?: ExposedToBrowserDescriptor<T>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const config: PluginConfigDescriptor<ConfigType> = {
| Property | Type | Description |
| --- | --- | --- |
| [deprecations?](./kibana-plugin-core-server.pluginconfigdescriptor.deprecations.md) | ConfigDeprecationProvider | <i>(Optional)</i> Provider for the to apply to the plugin configuration. |
| [exposeToBrowser?](./kibana-plugin-core-server.pluginconfigdescriptor.exposetobrowser.md) | { \[P in keyof T\]?: boolean; } | <i>(Optional)</i> List of configuration properties that will be available on the client-side plugin. |
| [exposeToBrowser?](./kibana-plugin-core-server.pluginconfigdescriptor.exposetobrowser.md) | ExposedToBrowserDescriptor&lt;T&gt; | <i>(Optional)</i> List of configuration properties that will be available on the client-side plugin. |
| [exposeToUsage?](./kibana-plugin-core-server.pluginconfigdescriptor.exposetousage.md) | MakeUsageFromSchema&lt;T&gt; | <i>(Optional)</i> Expose non-default configs to usage collection to be sent via telemetry. set a config to <code>true</code> to report the actual changed config value. set a config to <code>false</code> to report the changed config value as \[redacted\].<!-- -->All changed configs except booleans and numbers will be reported as \[redacted\] unless otherwise specified.[MakeUsageFromSchema](./kibana-plugin-core-server.makeusagefromschema.md) |
| [schema](./kibana-plugin-core-server.pluginconfigdescriptor.schema.md) | PluginConfigSchema&lt;T&gt; | Schema to use to validate the plugin configuration.[PluginConfigSchema](./kibana-plugin-core-server.pluginconfigschema.md) |

1 change: 1 addition & 0 deletions src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export type {
PluginName,
SharedGlobalConfig,
MakeUsageFromSchema,
ExposedToBrowserDescriptor,
} from './plugins';

export {
Expand Down
162 changes: 162 additions & 0 deletions src/core/server/plugins/create_browser_config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { ExposedToBrowserDescriptor } from './types';
import { createBrowserConfig } from './create_browser_config';

describe('createBrowserConfig', () => {
it('picks nothing by default', () => {
const config = {
foo: 'bar',
nested: {
str: 'string',
num: 42,
},
};
const descriptor: ExposedToBrowserDescriptor<typeof config> = {};

const browserConfig = createBrowserConfig(config, descriptor);

expect(browserConfig).toEqual({});
});

it('picks all the nested properties when using `true`', () => {
const config = {
foo: 'bar',
nested: {
str: 'string',
num: 42,
},
};

const descriptor: ExposedToBrowserDescriptor<typeof config> = {
foo: true,
nested: true,
};

const browserConfig = createBrowserConfig(config, descriptor);

expect(browserConfig).toEqual({
foo: 'bar',
nested: {
str: 'string',
num: 42,
},
});
});

it('picks specific nested properties when using a nested declaration', () => {
const config = {
foo: 'bar',
nested: {
str: 'string',
num: 42,
},
};

const descriptor: ExposedToBrowserDescriptor<typeof config> = {
foo: true,
nested: {
str: true,
num: false,
},
};

const browserConfig = createBrowserConfig(config, descriptor);

expect(browserConfig).toEqual({
foo: 'bar',
nested: {
str: 'string',
},
});
});

it('accepts deeply nested structures', () => {
const config = {
foo: 'bar',
deeply: {
str: 'string',
nested: {
hello: 'dolly',
structure: {
propA: 'propA',
propB: 'propB',
},
},
},
};

const descriptor: ExposedToBrowserDescriptor<typeof config> = {
foo: false,
deeply: {
str: false,
nested: {
hello: true,
structure: {
propA: true,
propB: false,
},
},
},
};

const browserConfig = createBrowserConfig(config, descriptor);

expect(browserConfig).toEqual({
deeply: {
nested: {
hello: 'dolly',
structure: {
propA: 'propA',
},
},
},
});
});

it('only includes leaf properties that are `true` when in nested structures', () => {
const config = {
foo: 'bar',
deeply: {
str: 'string',
nested: {
hello: 'dolly',
structure: {
propA: 'propA',
propB: 'propB',
},
},
},
};

const descriptor: ExposedToBrowserDescriptor<typeof config> = {
deeply: {
nested: {
hello: true,
structure: {
propA: true,
},
},
},
};

const browserConfig = createBrowserConfig(config, descriptor);

expect(browserConfig).toEqual({
deeply: {
nested: {
hello: 'dolly',
structure: {
propA: 'propA',
},
},
},
});
});
});
32 changes: 32 additions & 0 deletions src/core/server/plugins/create_browser_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { ExposedToBrowserDescriptor } from './types';

export const createBrowserConfig = <T = unknown>(
config: T,
descriptor: ExposedToBrowserDescriptor<T>
): unknown => {
return recursiveCreateConfig(config, descriptor);
};

const recursiveCreateConfig = <T = unknown>(
config: T,
descriptor: ExposedToBrowserDescriptor<T> = {}
): unknown => {
return Object.entries(config || {}).reduce((browserConfig, [key, value]) => {
const exposedConfig = descriptor[key as keyof ExposedToBrowserDescriptor<T>];
if (exposedConfig && typeof exposedConfig === 'object') {
browserConfig[key] = recursiveCreateConfig(value, exposedConfig);
}
if (exposedConfig === true) {
browserConfig[key] = value;
}
return browserConfig;
}, {} as Record<string, unknown>);
};
18 changes: 7 additions & 11 deletions src/core/server/plugins/plugins_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Path from 'path';
import { Observable } from 'rxjs';
import { filter, first, map, tap, toArray } from 'rxjs/operators';
import { getFlattenedObject, pick } from '@kbn/std';
import { getFlattenedObject } from '@kbn/std';

import { CoreService } from '../../types';
import { CoreContext } from '../core_context';
Expand All @@ -26,6 +26,7 @@ import {
} from './types';
import { PluginsConfig, PluginsConfigType } from './plugins_config';
import { PluginsSystem } from './plugins_system';
import { createBrowserConfig } from './create_browser_config';
import { InternalCorePreboot, InternalCoreSetup, InternalCoreStart } from '../internal_types';
import { IConfigService } from '../config';
import { InternalEnvironmentServicePreboot } from '../environment';
Expand Down Expand Up @@ -228,16 +229,11 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS
const configDescriptor = this.pluginConfigDescriptors.get(pluginId)!;
return [
pluginId,
this.configService.atPath(plugin.configPath).pipe(
map((config: any) =>
pick(
config || {},
Object.entries(configDescriptor.exposeToBrowser!)
.filter(([_, exposed]) => exposed)
.map(([key, _]) => key)
)
)
),
this.configService
.atPath(plugin.configPath)
.pipe(
map((config: any) => createBrowserConfig(config, configDescriptor.exposeToBrowser!))
),
];
})
);
Expand Down
90 changes: 90 additions & 0 deletions src/core/server/plugins/types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { ExposedToBrowserDescriptor } from './types';

describe('ExposedToBrowserDescriptor', () => {
interface ConfigType {
str: string;
array: number[];
obj: {
sub1: string;
sub2: number;
};
deep: {
foo: number;
nested: {
str: string;
arr: number[];
};
};
}

it('allows to use recursion on objects', () => {
const exposeToBrowser: ExposedToBrowserDescriptor<ConfigType> = {
obj: {
sub1: true,
},
};
expect(exposeToBrowser).toBeDefined();
});

it('allows to use recursion at multiple levels', () => {
const exposeToBrowser: ExposedToBrowserDescriptor<ConfigType> = {
deep: {
foo: true,
nested: {
str: true,
},
},
};
expect(exposeToBrowser).toBeDefined();
});

it('does not allow to use recursion on arrays', () => {
const exposeToBrowser: ExposedToBrowserDescriptor<ConfigType> = {
// @ts-expect-error Type '{ 0: true; }' is not assignable to type 'boolean | undefined'.
array: {
0: true,
},
};
expect(exposeToBrowser).toBeDefined();
});

it('does not allow to use recursion on arrays at lower levels', () => {
const exposeToBrowser: ExposedToBrowserDescriptor<ConfigType> = {
deep: {
nested: {
// @ts-expect-error Type '{ 0: true; }' is not assignable to type 'boolean | undefined'.
arr: {
0: true,
},
},
},
};
expect(exposeToBrowser).toBeDefined();
});

it('allows to specify all the properties', () => {
const exposeToBrowser: ExposedToBrowserDescriptor<ConfigType> = {
str: true,
array: false,
obj: {
sub1: true,
},
deep: {
foo: true,
nested: {
arr: false,
str: true,
},
},
};
expect(exposeToBrowser).toBeDefined();
});
});
Loading

0 comments on commit dd0a190

Please sign in to comment.