[8.1] Allow nested declaration for exposeToBrowser (#128864) (#128926)

* Allow nested declaration for `exposeToBrowser` (#128864)

* Allow nested declaration for `exposeToBrowser`

* update generated doc

* add utest

(cherry picked from commit dd0a19033f)

# Conflicts:
#	src/core/server/server.api.md

* update generated doc
This commit is contained in:
Pierre Gayvallet 2022-03-30 19:24:26 +02:00 committed by GitHub
parent fc773a387c
commit d2b47b1b13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 341 additions and 24 deletions

View file

@ -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;
};
```

View file

@ -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. |

View file

@ -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>;
```

View file

@ -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) |

View file

@ -268,6 +268,7 @@ export type {
PluginName,
SharedGlobalConfig,
MakeUsageFromSchema,
ExposedToBrowserDescriptor,
} from './plugins';
export {

View file

@ -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',
},
},
},
});
});
});

View file

@ -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>);
};

View file

@ -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';
@ -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';
@ -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!))
),
];
})
);

View file

@ -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();
});
});

View file

@ -26,6 +26,23 @@ type Maybe<T> = T | undefined;
*/
export type PluginConfigSchema<T> = Type<T>;
/**
* Type defining the list of configuration properties that will be exposed on the client-side
* Object properties can either be fully exposed
*
* @public
*/
export type ExposedToBrowserDescriptor<T> = {
[Key in keyof T]?: T[Key] extends Maybe<any[]>
? // handles arrays as primitive values
boolean
: T[Key] extends Maybe<object>
? // can be nested for objects
ExposedToBrowserDescriptor<T[Key]> | boolean
: // primitives
boolean;
};
/**
* Describes a plugin configuration properties.
*
@ -64,7 +81,7 @@ export interface PluginConfigDescriptor<T = any> {
/**
* List of configuration properties that will be available on the client-side plugin.
*/
exposeToBrowser?: { [P in keyof T]?: boolean };
exposeToBrowser?: ExposedToBrowserDescriptor<T>;
/**
* Schema to use to validate the plugin configuration.
*

View file

@ -1010,6 +1010,14 @@ export interface ExecutionContextSetup {
// @public (undocumented)
export type ExecutionContextStart = ExecutionContextSetup;
// Warning: (ae-forgotten-export) The symbol "Maybe" needs to be exported by the entry point index.d.ts
//
// @public
export 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;
};
// @public
export interface FakeRequest {
headers: Headers_2;
@ -1465,8 +1473,6 @@ export { LogMeta }
export { LogRecord }
// Warning: (ae-forgotten-export) The symbol "Maybe" needs to be exported by the entry point index.d.ts
//
// @public
export type MakeUsageFromSchema<T> = {
[Key in keyof T]?: T[Key] extends Maybe<object[]> ? false : T[Key] extends Maybe<any[]> ? boolean : T[Key] extends Maybe<object> ? MakeUsageFromSchema<T[Key]> | boolean : boolean;
@ -1658,9 +1664,7 @@ export { Plugin_2 as Plugin }
export interface PluginConfigDescriptor<T = any> {
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
deprecations?: ConfigDeprecationProvider;
exposeToBrowser?: {
[P in keyof T]?: boolean;
};
exposeToBrowser?: ExposedToBrowserDescriptor<T>;
exposeToUsage?: MakeUsageFromSchema<T>;
schema: PluginConfigSchema<T>;
}
@ -3173,8 +3177,8 @@ export const validBodyOutput: readonly ["data", "stream"];
//
// src/core/server/elasticsearch/client/types.ts:93:7 - (ae-forgotten-export) The symbol "Explanation" needs to be exported by the entry point index.d.ts
// src/core/server/http/router/response.ts:302:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:375:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:377:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:483:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "create"
// src/core/server/plugins/types.ts:392:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:394:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:500:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "create"
```