mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* Avoid loading all plugins on anonymous pages (#129555)
(cherry picked from commit 6acf7c0481
)
# Conflicts:
# src/core/server/server.api.md
# x-pack/plugins/security/kibana.json
# x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
# x-pack/plugins/security/public/plugin.tsx
* node scripts/check_published_api_changes.js --accept
This commit is contained in:
parent
8c3351c577
commit
f6972e420a
31 changed files with 255 additions and 57 deletions
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DiscoveredPlugin](./kibana-plugin-core-server.discoveredplugin.md) > [enabledOnAnonymousPages](./kibana-plugin-core-server.discoveredplugin.enabledonanonymouspages.md)
|
||||
|
||||
## DiscoveredPlugin.enabledOnAnonymousPages property
|
||||
|
||||
Specifies whether this plugin - and its required dependencies - will be enabled for anonymous pages (login page, status page when configured, etc.) Default is false.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly enabledOnAnonymousPages?: boolean;
|
||||
```
|
|
@ -17,6 +17,7 @@ export interface DiscoveredPlugin
|
|||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [configPath](./kibana-plugin-core-server.discoveredplugin.configpath.md) | ConfigPath | Root configuration path used by the plugin, defaults to "id" in snake\_case format. |
|
||||
| [enabledOnAnonymousPages?](./kibana-plugin-core-server.discoveredplugin.enabledonanonymouspages.md) | boolean | <i>(Optional)</i> Specifies whether this plugin - and its required dependencies - will be enabled for anonymous pages (login page, status page when configured, etc.) Default is false. |
|
||||
| [id](./kibana-plugin-core-server.discoveredplugin.id.md) | PluginName | Identifier of the plugin. |
|
||||
| [optionalPlugins](./kibana-plugin-core-server.discoveredplugin.optionalplugins.md) | readonly PluginName\[\] | An optional list of the other plugins that if installed and enabled \*\*may be\*\* leveraged by this plugin for some additional functionality but otherwise are not required for this plugin to work properly. |
|
||||
| [requiredBundles](./kibana-plugin-core-server.discoveredplugin.requiredbundles.md) | readonly PluginName\[\] | List of plugin ids that this plugin's UI code imports modules from that are not in <code>requiredPlugins</code>. |
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) > [includeUserSettings](./kibana-plugin-core-server.irenderoptions.includeusersettings.md)
|
||||
|
||||
## IRenderOptions.includeUserSettings property
|
||||
|
||||
Set whether to output user settings in the page metadata. `true` by default.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
includeUserSettings?: boolean;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) > [isAnonymousPage](./kibana-plugin-core-server.irenderoptions.isanonymouspage.md)
|
||||
|
||||
## IRenderOptions.isAnonymousPage property
|
||||
|
||||
Set whether the page is anonymous, which determines what plugins are enabled and whether to output user settings in the page metadata. `false` by default.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
isAnonymousPage?: boolean;
|
||||
```
|
|
@ -15,5 +15,5 @@ export interface IRenderOptions
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [includeUserSettings?](./kibana-plugin-core-server.irenderoptions.includeusersettings.md) | boolean | <i>(Optional)</i> Set whether to output user settings in the page metadata. <code>true</code> by default. |
|
||||
| [isAnonymousPage?](./kibana-plugin-core-server.irenderoptions.isanonymouspage.md) | boolean | <i>(Optional)</i> Set whether the page is anonymous, which determines what plugins are enabled and whether to output user settings in the page metadata. <code>false</code> by default. |
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) > [enabledOnAnonymousPages](./kibana-plugin-core-server.pluginmanifest.enabledonanonymouspages.md)
|
||||
|
||||
## PluginManifest.enabledOnAnonymousPages property
|
||||
|
||||
Specifies whether this plugin - and its required dependencies - will be enabled for anonymous pages (login page, status page when configured, etc.) Default is false.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly enabledOnAnonymousPages?: boolean;
|
||||
```
|
|
@ -22,6 +22,7 @@ Should never be used in code outside of Core but is exported for documentation p
|
|||
| --- | --- | --- |
|
||||
| [configPath](./kibana-plugin-core-server.pluginmanifest.configpath.md) | ConfigPath | Root used by the plugin, defaults to "id" in snake\_case format. |
|
||||
| [description?](./kibana-plugin-core-server.pluginmanifest.description.md) | string | <i>(Optional)</i> TODO: make required once all plugins specify this. A brief description of what this plugin does and any capabilities it provides. |
|
||||
| [enabledOnAnonymousPages?](./kibana-plugin-core-server.pluginmanifest.enabledonanonymouspages.md) | boolean | <i>(Optional)</i> Specifies whether this plugin - and its required dependencies - will be enabled for anonymous pages (login page, status page when configured, etc.) Default is false. |
|
||||
| [extraPublicDirs?](./kibana-plugin-core-server.pluginmanifest.extrapublicdirs.md) | string\[\] | <i>(Optional)</i> Specifies directory names that can be imported by other ui-plugins built using the same instance of the @<!-- -->kbn/optimizer. A temporary measure we plan to replace with better mechanisms for sharing static code between plugins |
|
||||
| [id](./kibana-plugin-core-server.pluginmanifest.id.md) | PluginName | Identifier of the plugin. Must be a string in camelCase. Part of a plugin public contract. Other plugins leverage it to access plugin API, navigate to the plugin, etc. |
|
||||
| [kibanaVersion](./kibana-plugin-core-server.pluginmanifest.kibanaversion.md) | string | The version of Kibana the plugin is compatible with, defaults to "version". |
|
||||
|
|
|
@ -38,6 +38,7 @@ interface Manifest {
|
|||
};
|
||||
// TODO: make required.
|
||||
description?: string;
|
||||
enabledOnAnonymousPages?: boolean;
|
||||
serviceFolders: readonly string[];
|
||||
requiredPlugins: readonly string[];
|
||||
optionalPlugins: readonly string[];
|
||||
|
@ -83,6 +84,7 @@ export function parseKibanaPlatformPlugin(manifestPath: string): KibanaPlatformP
|
|||
serviceFolders: manifest.serviceFolders || [],
|
||||
owner: manifest.owner,
|
||||
description: manifest.description,
|
||||
enabledOnAnonymousPages: Boolean(manifest.enabledOnAnonymousPages),
|
||||
requiredPlugins: isValidDepsDeclaration(manifest.requiredPlugins, 'requiredPlugins'),
|
||||
optionalPlugins: isValidDepsDeclaration(manifest.optionalPlugins, 'optionalPlugins'),
|
||||
requiredBundles: isValidDepsDeclaration(manifest.requiredBundles, 'requiredBundles'),
|
||||
|
|
|
@ -73,7 +73,7 @@ describe('HttpResources service', () => {
|
|||
kibanaRequest,
|
||||
context.core.uiSettings.client,
|
||||
{
|
||||
includeUserSettings: true,
|
||||
isAnonymousPage: false,
|
||||
vars: {
|
||||
apmConfig,
|
||||
},
|
||||
|
@ -119,7 +119,7 @@ describe('HttpResources service', () => {
|
|||
kibanaRequest,
|
||||
context.core.uiSettings.client,
|
||||
{
|
||||
includeUserSettings: false,
|
||||
isAnonymousPage: true,
|
||||
vars: {
|
||||
apmConfig,
|
||||
},
|
||||
|
|
|
@ -94,7 +94,7 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
|
|||
async renderCoreApp(options: HttpResourcesRenderOptions = {}) {
|
||||
const apmConfig = getApmConfig(request.url.pathname);
|
||||
const body = await deps.rendering.render(request, context.core.uiSettings.client, {
|
||||
includeUserSettings: true,
|
||||
isAnonymousPage: false,
|
||||
vars: {
|
||||
apmConfig,
|
||||
},
|
||||
|
@ -108,7 +108,7 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
|
|||
async renderAnonymousCoreApp(options: HttpResourcesRenderOptions = {}) {
|
||||
const apmConfig = getApmConfig(request.url.pathname);
|
||||
const body = await deps.rendering.render(request, context.core.uiSettings.client, {
|
||||
includeUserSettings: false,
|
||||
isAnonymousPage: true,
|
||||
vars: {
|
||||
apmConfig,
|
||||
},
|
||||
|
|
|
@ -407,6 +407,7 @@ test('return all set optional fields as they are in manifest', async () => {
|
|||
optionalPlugins: ['some-optional-plugin'],
|
||||
ui: true,
|
||||
owner: { name: 'foo' },
|
||||
enabledOnAnonymousPages: true,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
@ -424,6 +425,7 @@ test('return all set optional fields as they are in manifest', async () => {
|
|||
server: false,
|
||||
ui: true,
|
||||
owner: { name: 'foo' },
|
||||
enabledOnAnonymousPages: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ const KNOWN_MANIFEST_FIELDS = (() => {
|
|||
serviceFolders: true,
|
||||
owner: true,
|
||||
description: true,
|
||||
enabledOnAnonymousPages: true,
|
||||
};
|
||||
|
||||
return new Set(Object.keys(manifestFields));
|
||||
|
@ -212,6 +213,7 @@ export async function parseManifest(
|
|||
extraPublicDirs: manifest.extraPublicDirs,
|
||||
owner: manifest.owner!,
|
||||
description: manifest.description,
|
||||
enabledOnAnonymousPages: manifest.enabledOnAnonymousPages,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -262,6 +262,7 @@ export class PluginsSystem<T extends PluginType> {
|
|||
uiPluginNames.includes(p)
|
||||
),
|
||||
requiredBundles: plugin.manifest.requiredBundles,
|
||||
enabledOnAnonymousPages: plugin.manifest.enabledOnAnonymousPages,
|
||||
},
|
||||
];
|
||||
})
|
||||
|
|
|
@ -260,6 +260,12 @@ export interface PluginManifest {
|
|||
* A brief description of what this plugin does and any capabilities it provides.
|
||||
*/
|
||||
readonly description?: string;
|
||||
|
||||
/**
|
||||
* Specifies whether this plugin - and its required dependencies - will be enabled for anonymous pages (login page, status page when
|
||||
* configured, etc.) Default is false.
|
||||
*/
|
||||
readonly enabledOnAnonymousPages?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -307,6 +313,12 @@ export interface DiscoveredPlugin {
|
|||
* duplicated here.
|
||||
*/
|
||||
readonly requiredBundles: readonly PluginName[];
|
||||
|
||||
/**
|
||||
* Specifies whether this plugin - and its required dependencies - will be enabled for anonymous pages (login page, status page when
|
||||
* configured, etc.) Default is false.
|
||||
*/
|
||||
readonly enabledOnAnonymousPages?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -186,18 +186,22 @@ describe('bootstrapRenderer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls getPluginsBundlePaths with the correct parameters', async () => {
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
[false, true].forEach((isAnonymousPage) => {
|
||||
it(`calls getPluginsBundlePaths with the correct parameters when isAnonymousPage=${isAnonymousPage}`, async () => {
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
await renderer({
|
||||
request,
|
||||
uiSettingsClient,
|
||||
});
|
||||
await renderer({
|
||||
request,
|
||||
uiSettingsClient,
|
||||
isAnonymousPage,
|
||||
});
|
||||
|
||||
expect(getPluginsBundlePathsMock).toHaveBeenCalledTimes(1);
|
||||
expect(getPluginsBundlePathsMock).toHaveBeenCalledWith({
|
||||
uiPlugins,
|
||||
regularBundlePath: '/base-path/42/bundles',
|
||||
expect(getPluginsBundlePathsMock).toHaveBeenCalledTimes(1);
|
||||
expect(getPluginsBundlePathsMock).toHaveBeenCalledWith({
|
||||
isAnonymousPage,
|
||||
uiPlugins,
|
||||
regularBundlePath: '/base-path/42/bundles',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ interface FactoryOptions {
|
|||
interface RenderedOptions {
|
||||
request: KibanaRequest;
|
||||
uiSettingsClient: IUiSettingsClient;
|
||||
isAnonymousPage?: boolean;
|
||||
}
|
||||
|
||||
interface RendererResult {
|
||||
|
@ -48,7 +49,7 @@ export const bootstrapRendererFactory: BootstrapRendererFactory = ({
|
|||
return authStatus !== 'unauthenticated';
|
||||
};
|
||||
|
||||
return async function bootstrapRenderer({ uiSettingsClient, request }) {
|
||||
return async function bootstrapRenderer({ uiSettingsClient, request, isAnonymousPage = false }) {
|
||||
let darkMode = false;
|
||||
let themeVersion = 'v8';
|
||||
|
||||
|
@ -70,6 +71,7 @@ export const bootstrapRendererFactory: BootstrapRendererFactory = ({
|
|||
const bundlePaths = getPluginsBundlePaths({
|
||||
uiPlugins,
|
||||
regularBundlePath,
|
||||
isAnonymousPage,
|
||||
});
|
||||
|
||||
const jsDependencyPaths = getJsDependencyPaths(regularBundlePath, bundlePaths);
|
||||
|
|
|
@ -50,6 +50,7 @@ describe('getPluginsBundlePaths', () => {
|
|||
a: ['b', 'c'],
|
||||
b: ['d'],
|
||||
}),
|
||||
isAnonymousPage: false, // This parameter is passed to filterUiPlugins, we have separate tests for that function
|
||||
});
|
||||
|
||||
expect([...pluginBundlePaths.keys()].sort()).toEqual(['a', 'b', 'c', 'd']);
|
||||
|
@ -61,6 +62,7 @@ describe('getPluginsBundlePaths', () => {
|
|||
uiPlugins: createUiPlugins({
|
||||
a: ['b'],
|
||||
}),
|
||||
isAnonymousPage: false,
|
||||
});
|
||||
|
||||
expect(pluginBundlePaths.get('a')).toEqual({
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import { UiPlugins } from '../../plugins';
|
||||
import { filterUiPlugins } from '../filter_ui_plugins';
|
||||
|
||||
export interface PluginInfo {
|
||||
publicPath: string;
|
||||
|
@ -16,12 +17,14 @@ export interface PluginInfo {
|
|||
export const getPluginsBundlePaths = ({
|
||||
uiPlugins,
|
||||
regularBundlePath,
|
||||
isAnonymousPage,
|
||||
}: {
|
||||
uiPlugins: UiPlugins;
|
||||
regularBundlePath: string;
|
||||
isAnonymousPage: boolean;
|
||||
}) => {
|
||||
const pluginBundlePaths = new Map<string, PluginInfo>();
|
||||
const pluginsToProcess = [...uiPlugins.public.keys()];
|
||||
const pluginsToProcess = filterUiPlugins({ uiPlugins, isAnonymousPage }).map(([id]) => id);
|
||||
|
||||
while (pluginsToProcess.length > 0) {
|
||||
const pluginId = pluginsToProcess.pop() as string;
|
||||
|
|
|
@ -20,7 +20,6 @@ export const registerBootstrapRoute = ({
|
|||
{
|
||||
path: '/bootstrap.js',
|
||||
options: {
|
||||
authRequired: 'optional',
|
||||
tags: ['api'],
|
||||
},
|
||||
validate: false,
|
||||
|
@ -29,6 +28,33 @@ export const registerBootstrapRoute = ({
|
|||
const uiSettingsClient = ctx.core.uiSettings.client;
|
||||
const { body, etag } = await renderer({ uiSettingsClient, request: req });
|
||||
|
||||
return res.ok({
|
||||
body,
|
||||
headers: {
|
||||
etag,
|
||||
'content-type': 'application/javascript',
|
||||
'cache-control': 'must-revalidate',
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
router.get(
|
||||
{
|
||||
path: '/bootstrap-anonymous.js',
|
||||
options: {
|
||||
authRequired: 'optional',
|
||||
tags: ['api'],
|
||||
},
|
||||
validate: false,
|
||||
},
|
||||
async (ctx, req, res) => {
|
||||
const uiSettingsClient = ctx.core.uiSettings.client;
|
||||
const { body, etag } = await renderer({
|
||||
uiSettingsClient,
|
||||
request: req,
|
||||
isAnonymousPage: true,
|
||||
});
|
||||
|
||||
return res.ok({
|
||||
body,
|
||||
headers: {
|
||||
|
|
46
src/core/server/rendering/filter_ui_plugins.test.ts
Normal file
46
src/core/server/rendering/filter_ui_plugins.test.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 type { DiscoveredPlugin, PluginName, UiPlugins } from '../plugins';
|
||||
import { filterUiPlugins } from './filter_ui_plugins';
|
||||
|
||||
function createMockPlugin(params: Partial<DiscoveredPlugin>) {
|
||||
const { requiredPlugins, optionalPlugins, enabledOnAnonymousPages } = params;
|
||||
// other DiscoveredPlugin fields don't matter for this test suite
|
||||
return {
|
||||
requiredPlugins,
|
||||
optionalPlugins,
|
||||
enabledOnAnonymousPages,
|
||||
} as DiscoveredPlugin;
|
||||
}
|
||||
|
||||
describe('filterUiPlugins', () => {
|
||||
const uiPlugins = {
|
||||
// other UiPlugin fields don't matter for this test suite
|
||||
public: new Map<PluginName, DiscoveredPlugin>()
|
||||
.set('one', createMockPlugin({ requiredPlugins: [] }))
|
||||
.set('two', createMockPlugin({ requiredPlugins: ['three'] }))
|
||||
.set('three', createMockPlugin({ requiredPlugins: ['four'], enabledOnAnonymousPages: true }))
|
||||
.set('four', createMockPlugin({ requiredPlugins: ['five'] }))
|
||||
.set('five', createMockPlugin({ requiredPlugins: [], optionalPlugins: ['six'] })) // optional plugin dependencies are ignored
|
||||
.set('six', createMockPlugin({ requiredPlugins: [] })),
|
||||
} as UiPlugins;
|
||||
|
||||
it('does not filter any plugins when isAnonymousPage=false', () => {
|
||||
// The return value should contain all plugins in the same order.
|
||||
const result = filterUiPlugins({ uiPlugins, isAnonymousPage: false });
|
||||
expect(result.map(([id]) => id)).toEqual(['one', 'two', 'three', 'four', 'five', 'six']);
|
||||
});
|
||||
|
||||
it('correctly filters plugins when isAnonymousPage=true', () => {
|
||||
// The return value should contain only (A) plugins that have enabledOnAnonymousPages=true, and (B) plugins that are required by A.
|
||||
// They should also still be in the same order.
|
||||
const result = filterUiPlugins({ uiPlugins, isAnonymousPage: true });
|
||||
expect(result.map(([id]) => id)).toEqual(['three', 'four', 'five']);
|
||||
});
|
||||
});
|
50
src/core/server/rendering/filter_ui_plugins.ts
Normal file
50
src/core/server/rendering/filter_ui_plugins.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 type { UiPlugins } from '../plugins';
|
||||
|
||||
/**
|
||||
* Gets the array of plugins that should be enabled on the page.
|
||||
* * If this _is not_ an anonymous page, all plugins will be enabled.
|
||||
* * If this _is_ an anonymous page, only plugins that have "enabledOnAnonymousPages" set in their manifest *and* the graph of their
|
||||
* requiredPlugins will be enabled.
|
||||
*/
|
||||
export function filterUiPlugins({
|
||||
uiPlugins,
|
||||
isAnonymousPage,
|
||||
}: {
|
||||
uiPlugins: UiPlugins;
|
||||
isAnonymousPage: boolean;
|
||||
}) {
|
||||
if (!isAnonymousPage) {
|
||||
return [...uiPlugins.public];
|
||||
}
|
||||
const pluginsToProcess = [...uiPlugins.public].reduce<string[]>((acc, [id, plugin]) => {
|
||||
if (plugin.enabledOnAnonymousPages) {
|
||||
acc.push(id);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const pluginsToInclude = new Set<string>();
|
||||
while (pluginsToProcess.length > 0) {
|
||||
const pluginId = pluginsToProcess.pop() as string;
|
||||
const plugin = uiPlugins.public.get(pluginId);
|
||||
if (!plugin) {
|
||||
continue;
|
||||
}
|
||||
pluginsToInclude.add(pluginId);
|
||||
for (const requiredPluginId of plugin.requiredPlugins) {
|
||||
if (!pluginsToInclude.has(requiredPluginId)) {
|
||||
pluginsToProcess.push(requiredPluginId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Filter the plugins, maintaining the order (that is important)
|
||||
return [...uiPlugins.public].filter(([id]) => pluginsToInclude.has(id));
|
||||
}
|
|
@ -87,24 +87,29 @@ function renderTestCases(
|
|||
});
|
||||
|
||||
it('renders "core" page driven by settings', async () => {
|
||||
uiSettings.getUserProvided.mockResolvedValue({ 'theme:darkMode': { userValue: true } });
|
||||
const userSettings = { 'theme:darkMode': { userValue: true } };
|
||||
uiSettings.getUserProvided.mockResolvedValue(userSettings);
|
||||
const [render] = await getRender();
|
||||
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);
|
||||
expect(data.legacyMetadata.uiSettings.user).toEqual(userSettings); // user settings are injected
|
||||
});
|
||||
|
||||
it('renders "core" with excluded user settings', async () => {
|
||||
const userSettings = { 'theme:darkMode': { userValue: true } };
|
||||
uiSettings.getUserProvided.mockResolvedValue(userSettings);
|
||||
const [render] = await getRender();
|
||||
const content = await render(createKibanaRequest(), uiSettings, {
|
||||
includeUserSettings: false,
|
||||
isAnonymousPage: true,
|
||||
});
|
||||
const dom = load(content);
|
||||
const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""');
|
||||
|
||||
expect(data).toMatchSnapshot(INJECTED_METADATA);
|
||||
expect(data.legacyMetadata.uiSettings.user).toEqual({}); // user settings are not injected
|
||||
});
|
||||
|
||||
it('calls `getStylesheetPaths` with the correct parameters', async () => {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { renderToStaticMarkup } from 'react-dom/server';
|
|||
import { take } from 'rxjs/operators';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { UiPlugins } from '../plugins';
|
||||
import type { UiPlugins } from '../plugins';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { Template } from './views';
|
||||
import {
|
||||
|
@ -26,6 +26,7 @@ import { registerBootstrapRoute, bootstrapRendererFactory } from './bootstrap';
|
|||
import { getSettingValue, getStylesheetPaths } from './render_utils';
|
||||
import { KibanaRequest } from '../http';
|
||||
import { IUiSettingsClient } from '../ui_settings';
|
||||
import { filterUiPlugins } from './filter_ui_plugins';
|
||||
|
||||
type RenderOptions = (RenderingPrebootDeps & { status?: never }) | RenderingSetupDeps;
|
||||
|
||||
|
@ -78,7 +79,7 @@ export class RenderingService {
|
|||
{ http, uiPlugins, status }: RenderOptions,
|
||||
request: KibanaRequest,
|
||||
uiSettings: IUiSettingsClient,
|
||||
{ includeUserSettings = true, vars }: IRenderOptions = {}
|
||||
{ isAnonymousPage = false, vars }: IRenderOptions = {}
|
||||
) {
|
||||
const env = {
|
||||
mode: this.coreContext.env.mode,
|
||||
|
@ -89,7 +90,7 @@ export class RenderingService {
|
|||
const { serverBasePath, publicBaseUrl } = http.basePath;
|
||||
const settings = {
|
||||
defaults: uiSettings.getRegistered() ?? {},
|
||||
user: includeUserSettings ? await uiSettings.getUserProvided() : {},
|
||||
user: isAnonymousPage ? {} : await uiSettings.getUserProvided(),
|
||||
};
|
||||
|
||||
const darkMode = getSettingValue('theme:darkMode', settings, Boolean);
|
||||
|
@ -102,10 +103,12 @@ export class RenderingService {
|
|||
buildNum,
|
||||
});
|
||||
|
||||
const filteredPlugins = filterUiPlugins({ uiPlugins, isAnonymousPage });
|
||||
const bootstrapScript = isAnonymousPage ? 'bootstrap-anonymous.js' : 'bootstrap.js';
|
||||
const metadata: RenderingMetadata = {
|
||||
strictCsp: http.csp.strict,
|
||||
uiPublicUrl: `${basePath}/ui`,
|
||||
bootstrapScriptUrl: `${basePath}/bootstrap.js`,
|
||||
bootstrapScriptUrl: `${basePath}/${bootstrapScript}`,
|
||||
i18n: i18n.translate,
|
||||
locale: i18n.getLocale(),
|
||||
darkMode,
|
||||
|
@ -127,7 +130,7 @@ export class RenderingService {
|
|||
externalUrl: http.externalUrl,
|
||||
vars: vars ?? {},
|
||||
uiPlugins: await Promise.all(
|
||||
[...uiPlugins.public].map(async ([id, plugin]) => ({
|
||||
filteredPlugins.map(async ([id, plugin]) => ({
|
||||
id,
|
||||
plugin,
|
||||
config: await getUiConfig(uiPlugins, id),
|
||||
|
|
|
@ -74,10 +74,10 @@ export interface RenderingSetupDeps {
|
|||
/** @public */
|
||||
export interface IRenderOptions {
|
||||
/**
|
||||
* Set whether to output user settings in the page metadata.
|
||||
* `true` by default.
|
||||
* Set whether the page is anonymous, which determines what plugins are enabled and whether to output user settings in the page metadata.
|
||||
* `false` by default.
|
||||
*/
|
||||
includeUserSettings?: boolean;
|
||||
isAnonymousPage?: boolean;
|
||||
|
||||
/**
|
||||
* Inject custom vars into the page metadata.
|
||||
|
|
|
@ -835,6 +835,7 @@ export type DestructiveRouteMethod = 'post' | 'put' | 'delete' | 'patch';
|
|||
// @public
|
||||
export interface DiscoveredPlugin {
|
||||
readonly configPath: ConfigPath;
|
||||
readonly enabledOnAnonymousPages?: boolean;
|
||||
readonly id: PluginName;
|
||||
readonly optionalPlugins: readonly PluginName[];
|
||||
readonly requiredBundles: readonly PluginName[];
|
||||
|
@ -1226,7 +1227,7 @@ export interface IntervalHistogram {
|
|||
|
||||
// @public (undocumented)
|
||||
export interface IRenderOptions {
|
||||
includeUserSettings?: boolean;
|
||||
isAnonymousPage?: boolean;
|
||||
// @internal @deprecated
|
||||
vars?: Record<string, any>;
|
||||
}
|
||||
|
@ -1633,6 +1634,7 @@ export interface PluginManifest {
|
|||
// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver
|
||||
readonly configPath: ConfigPath;
|
||||
readonly description?: string;
|
||||
readonly enabledOnAnonymousPages?: boolean;
|
||||
// @deprecated
|
||||
readonly extraPublicDirs?: string[];
|
||||
readonly id: PluginName;
|
||||
|
@ -3037,9 +3039,9 @@ export const validBodyOutput: readonly ["data", "stream"];
|
|||
//
|
||||
// src/core/server/elasticsearch/client/types.ts:94: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:394:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:394:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:397:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:503: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:406:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:406:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:409:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts
|
||||
// src/core/server/plugins/types.ts:515:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "create"
|
||||
|
||||
```
|
||||
|
|
|
@ -18,7 +18,7 @@ export class RenderingPlugin implements Plugin {
|
|||
validate: {
|
||||
query: schema.object(
|
||||
{
|
||||
includeUserSettings: schema.boolean({ defaultValue: true }),
|
||||
isAnonymousPage: schema.boolean({ defaultValue: false }),
|
||||
},
|
||||
{ unknowns: 'allow' }
|
||||
),
|
||||
|
@ -28,12 +28,12 @@ export class RenderingPlugin implements Plugin {
|
|||
},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const { includeUserSettings } = req.query;
|
||||
const { isAnonymousPage } = req.query;
|
||||
|
||||
if (includeUserSettings) {
|
||||
return res.renderCoreApp();
|
||||
if (isAnonymousPage) {
|
||||
return res.renderAnonymousCoreApp();
|
||||
}
|
||||
return res.renderAnonymousCoreApp();
|
||||
return res.renderCoreApp();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
});
|
||||
|
||||
it('renders "core" application without user settings', async () => {
|
||||
await navigateTo('/render/core?includeUserSettings=false');
|
||||
await navigateTo('/render/core?isAnonymousPage=true');
|
||||
|
||||
const [loadingMessage, userSettings] = await Promise.all([
|
||||
findLoadingMessage(),
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["xpack", "security"],
|
||||
"requiredPlugins": ["data", "features", "licensing", "taskManager"],
|
||||
"optionalPlugins": ["home", "management", "usageCollection", "spaces", "share"],
|
||||
"requiredPlugins": ["features", "licensing", "taskManager"],
|
||||
"optionalPlugins": ["data", "home", "management", "usageCollection", "spaces", "share"],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"enabledOnAnonymousPages": true,
|
||||
"requiredBundles": [
|
||||
"home",
|
||||
"kibanaReact",
|
||||
|
|
|
@ -69,7 +69,7 @@ import { RoleValidator } from './validate_role';
|
|||
interface Props {
|
||||
action: 'edit' | 'clone';
|
||||
roleName?: string;
|
||||
indexPatterns: IndexPatternsContract;
|
||||
indexPatterns?: IndexPatternsContract;
|
||||
userAPIClient: PublicMethodsOf<UserAPIClient>;
|
||||
indicesAPIClient: PublicMethodsOf<IndicesAPIClient>;
|
||||
rolesAPIClient: PublicMethodsOf<RolesAPIClient>;
|
||||
|
@ -287,6 +287,13 @@ export const EditRolePage: FunctionComponent<Props> = ({
|
|||
history,
|
||||
spacesApiUi,
|
||||
}) => {
|
||||
if (!indexPatterns) {
|
||||
// The indexPatterns plugin is technically marked as an optional dependency because we don't need to pull it in for Anonymous pages (such
|
||||
// as the login page). That said, it _is_ required for this page to function correctly, so we throw an error here if it's not available.
|
||||
// We don't ever expect Kibana to work correctly if the indexPatterns plugin is not available (and we don't expect this to happen at all),
|
||||
// so this error edge case is an acceptable tradeoff.
|
||||
throw new Error('The indexPatterns plugin is required for this page, but it is not available');
|
||||
}
|
||||
const backToRoleList = useCallback(() => history.push('/'), [history]);
|
||||
|
||||
// We should keep the same mutable instance of Validator for every re-render since we'll
|
||||
|
|
|
@ -105,7 +105,7 @@ export const rolesManagementApp = Object.freeze({
|
|||
license={license}
|
||||
docLinks={docLinks}
|
||||
uiCapabilities={application.capabilities}
|
||||
indexPatterns={data.indexPatterns}
|
||||
indexPatterns={data?.indexPatterns}
|
||||
history={history}
|
||||
spacesApiUi={spacesApiUi}
|
||||
/>
|
||||
|
|
|
@ -39,8 +39,8 @@ export interface PluginSetupDependencies {
|
|||
}
|
||||
|
||||
export interface PluginStartDependencies {
|
||||
data: DataPublicPluginStart;
|
||||
features: FeaturesPluginStart;
|
||||
data?: DataPublicPluginStart;
|
||||
management?: ManagementStart;
|
||||
spaces?: SpacesPluginStart;
|
||||
share?: SharePluginStart;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue