kibana/x-pack/plugins/features/server/plugin.ts
Pierre Gayvallet 8ce205999a
features plugin: improve capabilities caching (#153743)
## Summary

- Avoid constructing the capabilities passed to core's capability
provider for each request
- Add proper *explicit* lock mechanism to the feature registry
- Some other minor cleanup, probably

#### Before:

(look at the `getFeatureUICapabilities` block)

<img width="1629" alt="CleanShot 2023-03-26 at 09 55 45@2x"
src="https://user-images.githubusercontent.com/1532934/227951476-1edbd2bc-f60b-4529-8c22-ff0d9511b2b8.png">

#### After

<img width="1640" alt="CleanShot 2023-03-27 at 14 58 31@2x"
src="https://user-images.githubusercontent.com/1532934/227951533-dbe675c3-7089-4b36-86a9-584a3e97c860.png">
2023-03-28 10:01:43 +02:00

175 lines
5.6 KiB
TypeScript

/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { RecursiveReadonly } from '@kbn/utility-types';
import { deepFreeze } from '@kbn/std';
import {
CoreSetup,
CoreStart,
SavedObjectsServiceStart,
Logger,
Plugin,
PluginInitializerContext,
Capabilities as UICapabilities,
} from '@kbn/core/server';
import { FeatureRegistry } from './feature_registry';
import { uiCapabilitiesForFeatures } from './ui_capabilities_for_features';
import { buildOSSFeatures } from './oss_features';
import { defineRoutes } from './routes';
import {
ElasticsearchFeatureConfig,
ElasticsearchFeature,
KibanaFeature,
KibanaFeatureConfig,
} from '../common';
import type {
FeaturePrivilegeIterator,
SubFeaturePrivilegeIterator,
} from './feature_privilege_iterator';
import {
featurePrivilegeIterator,
subFeaturePrivilegeIterator,
} from './feature_privilege_iterator';
/**
* Describes public Features plugin contract returned at the `setup` stage.
*/
export interface PluginSetupContract {
registerKibanaFeature(feature: KibanaFeatureConfig): void;
registerElasticsearchFeature(feature: ElasticsearchFeatureConfig): void;
/**
* Calling this function during setup will crash Kibana.
* Use start contract instead.
* @deprecated
* @removeBy 8.8.0
*/
getKibanaFeatures(): KibanaFeature[];
/**
* Calling this function during setup will crash Kibana.
* Use start contract instead.
* @deprecated
* @removeBy 8.8.0
*/
getElasticsearchFeatures(): ElasticsearchFeature[];
/**
* In the future, OSS features should register their own subfeature
* privileges. This can be done when parts of Reporting are moved to
* src/plugins. For now, this method exists for `reporting` to tell
* `features` to include Reporting when registering OSS features.
*/
enableReportingUiCapabilities(): void;
/**
* Utility for iterating through all privileges belonging to a specific feature.
* {@see FeaturePrivilegeIterator }
*/
featurePrivilegeIterator: FeaturePrivilegeIterator;
/**
* Utility for iterating through all sub-feature privileges belonging to a specific feature.
* {@see SubFeaturePrivilegeIterator }
*/
subFeaturePrivilegeIterator: SubFeaturePrivilegeIterator;
}
export interface PluginStartContract {
getElasticsearchFeatures(): ElasticsearchFeature[];
getKibanaFeatures(): KibanaFeature[];
}
/**
* Represents Features Plugin instance that will be managed by the Kibana plugin system.
*/
export class FeaturesPlugin
implements Plugin<RecursiveReadonly<PluginSetupContract>, RecursiveReadonly<PluginStartContract>>
{
private readonly logger: Logger;
private readonly featureRegistry: FeatureRegistry = new FeatureRegistry();
private isReportingEnabled: boolean = false;
private capabilities?: UICapabilities;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.logger = this.initializerContext.logger.get();
}
public setup(core: CoreSetup): RecursiveReadonly<PluginSetupContract> {
defineRoutes({
router: core.http.createRouter(),
featureRegistry: this.featureRegistry,
});
// capabilities provider wil never be called before plugin start
core.capabilities.registerProvider(() => this.capabilities!);
return deepFreeze({
registerKibanaFeature: this.featureRegistry.registerKibanaFeature.bind(this.featureRegistry),
registerElasticsearchFeature: this.featureRegistry.registerElasticsearchFeature.bind(
this.featureRegistry
),
getKibanaFeatures: this.featureRegistry.getAllKibanaFeatures.bind(this.featureRegistry),
getElasticsearchFeatures: this.featureRegistry.getAllElasticsearchFeatures.bind(
this.featureRegistry
),
enableReportingUiCapabilities: this.enableReportingUiCapabilities.bind(this),
featurePrivilegeIterator,
subFeaturePrivilegeIterator,
});
}
public start(core: CoreStart): RecursiveReadonly<PluginStartContract> {
this.registerOssFeatures(core.savedObjects);
this.featureRegistry.lockRegistration();
this.capabilities = uiCapabilitiesForFeatures(
this.featureRegistry.getAllKibanaFeatures(),
this.featureRegistry.getAllElasticsearchFeatures()
);
return deepFreeze({
getElasticsearchFeatures: this.featureRegistry.getAllElasticsearchFeatures.bind(
this.featureRegistry
),
getKibanaFeatures: this.featureRegistry.getAllKibanaFeatures.bind(this.featureRegistry),
});
}
public stop() {}
private registerOssFeatures(savedObjects: SavedObjectsServiceStart) {
const registry = savedObjects.getTypeRegistry();
const savedObjectVisibleTypes = registry.getVisibleTypes().map((t) => t.name);
const savedObjectImportableAndExportableHiddenTypes = registry
.getImportableAndExportableTypes()
.filter((t) => registry.isHidden(t.name))
.map((t) => t.name);
const savedObjectTypes = Array.from(
new Set([...savedObjectVisibleTypes, ...savedObjectImportableAndExportableHiddenTypes])
);
const features = buildOSSFeatures({
savedObjectTypes,
includeReporting: this.isReportingEnabled,
});
for (const feature of features) {
this.featureRegistry.registerKibanaFeature(feature);
}
}
private enableReportingUiCapabilities() {
this.logger.debug(
`Feature controls for Reporting plugin are enabled. Please assign access to Reporting use Kibana feature controls for applications.`
);
this.isReportingEnabled = true;
}
}