-
Notifications
You must be signed in to change notification settings - Fork 8.5k
Allow nested declaration for exposeToBrowser
#128864
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [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; | ||
| }; | ||
| ``` |
| 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', | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| }); | ||
| }); | ||
| 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>); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mind the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're validating that at least one value exists (not a key), but that doesn't change much: Yea, I think we're fine. Only edge case I see would be a scenario where the descriptor only defines nested keys with false values: {
nested: {
key: false,
}
}this would 'bypass' the filtering. But the output of TBH we could probably get rid of this whole Do you think it would make sense to get rid of this |
||
| .atPath(plugin.configPath) | ||
| .pipe( | ||
| map((config: any) => createBrowserConfig(config, configDescriptor.exposeToBrowser!)) | ||
| ), | ||
| ]; | ||
| }) | ||
| ); | ||
|
|
||
| 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(); | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.