[7.17] Avoid loading all plugins on anonymous pages (#129555) (#129743)

* 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:
Joe Portner 2022-04-07 11:11:13 -04:00 committed by GitHub
parent 8c3351c577
commit f6972e420a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 255 additions and 57 deletions

View file

@ -0,0 +1,13 @@
<!-- 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; [DiscoveredPlugin](./kibana-plugin-core-server.discoveredplugin.md) &gt; [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;
```

View file

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

View file

@ -1,13 +0,0 @@
<!-- 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; [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) &gt; [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;
```

View file

@ -0,0 +1,13 @@
<!-- 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; [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) &gt; [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;
```

View file

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

View file

@ -0,0 +1,13 @@
<!-- 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; [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) &gt; [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;
```

View file

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

View file

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

View file

@ -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,
},

View file

@ -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,
},

View file

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

View file

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

View file

@ -262,6 +262,7 @@ export class PluginsSystem<T extends PluginType> {
uiPluginNames.includes(p)
),
requiredBundles: plugin.manifest.requiredBundles,
enabledOnAnonymousPages: plugin.manifest.enabledOnAnonymousPages,
},
];
})

View file

@ -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;
}
/**

View file

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

View file

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

View file

@ -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({

View file

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

View file

@ -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: {

View 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']);
});
});

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

View file

@ -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 () => {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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",

View file

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

View file

@ -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}
/>

View file

@ -39,8 +39,8 @@ export interface PluginSetupDependencies {
}
export interface PluginStartDependencies {
data: DataPublicPluginStart;
features: FeaturesPluginStart;
data?: DataPublicPluginStart;
management?: ManagementStart;
spaces?: SpacesPluginStart;
share?: SharePluginStart;