kibana/x-pack/plugins/spaces/server/plugin.ts
Sébastien Loix db2adf7588
[8.x] [Stateful sidenav] Welcome tour (#194926) (#196298)
# Backport

This will backport the following commits from `main` to `8.x`:
- [[Stateful sidenav] Welcome tour
(#194926)](https://github.com/elastic/kibana/pull/194926)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Sébastien
Loix","email":"sebastien.loix@elastic.co"},"sourceCommit":{"committedDate":"2024-10-15T12:18:30Z","message":"[Stateful
sidenav] Welcome tour
(#194926)","sha":"8cceaee0f42c6c0e7ee064ef98a0e652fd77e286","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Security/Spaces","release_note:skip","v9.0.0","Team:SharedUX","backport:prev-minor"],"number":194926,"url":"https://github.com/elastic/kibana/pull/194926","mergeCommit":{"message":"[Stateful
sidenav] Welcome tour
(#194926)","sha":"8cceaee0f42c6c0e7ee064ef98a0e652fd77e286"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194926","number":194926,"mergeCommit":{"message":"[Stateful
sidenav] Welcome tour
(#194926)","sha":"8cceaee0f42c6c0e7ee064ef98a0e652fd77e286"}}]}]
BACKPORT-->
2024-10-15 09:35:19 -05:00

259 lines
8.3 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 type { Observable } from 'rxjs';
import { BehaviorSubject, combineLatest, map } from 'rxjs';
import type { CloudSetup } from '@kbn/cloud-plugin/server';
import type {
CoreSetup,
CoreStart,
Logger,
Plugin,
PluginInitializerContext,
} from '@kbn/core/server';
import type { FeaturesPluginSetup, FeaturesPluginStart } from '@kbn/features-plugin/server';
import type { HomeServerPluginSetup } from '@kbn/home-plugin/server';
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { setupCapabilities } from './capabilities';
import type { ConfigType } from './config';
import { DefaultSpaceService } from './default_space';
import { initSpacesRequestInterceptors } from './lib/request_interceptors';
import { createSpacesTutorialContextFactory } from './lib/spaces_tutorial_context_factory';
import { initExternalSpacesApi } from './routes/api/external';
import { initInternalSpacesApi } from './routes/api/internal';
import { initSpacesViewsRoutes } from './routes/views';
import { SpacesSavedObjectsService } from './saved_objects';
import type { SpacesClientRepositoryFactory, SpacesClientWrapper } from './spaces_client';
import { SpacesClientService } from './spaces_client';
import type { SpacesServiceSetup, SpacesServiceStart } from './spaces_service';
import { SpacesService } from './spaces_service';
import type { SpacesRequestHandlerContext } from './types';
import { getUiSettings } from './ui_settings';
import { registerSpacesUsageCollector } from './usage_collection';
import { UsageStatsService } from './usage_stats';
import { SpacesLicenseService } from '../common/licensing';
export interface PluginsSetup {
features: FeaturesPluginSetup;
licensing: LicensingPluginSetup;
usageCollection?: UsageCollectionSetup;
home?: HomeServerPluginSetup;
cloud?: CloudSetup;
}
export interface PluginsStart {
features: FeaturesPluginStart;
}
/**
* Setup contract for the Spaces plugin.
*/
export interface SpacesPluginSetup {
/**
* Service for interacting with spaces.
*/
spacesService: SpacesServiceSetup;
/**
* Registries exposed for the security plugin to transparently provide authorization and audit logging.
* @private
*/
spacesClient: {
/**
* Sets the client repository factory.
* @private
*/
setClientRepositoryFactory: (factory: SpacesClientRepositoryFactory) => void;
/**
* Registers a client wrapper.
* @private
*/
registerClientWrapper: (wrapper: SpacesClientWrapper) => void;
};
/**
* Determines whether Kibana supports multiple spaces or only the default space.
*
* When `xpack.spaces.maxSpaces` is set to 1 Kibana only supports the default space and any spaces related UI can safely be hidden.
*/
hasOnlyDefaultSpace$: Observable<boolean>;
}
/**
* Start contract for the Spaces plugin.
*/
export interface SpacesPluginStart {
/** Service for interacting with spaces. */
spacesService: SpacesServiceStart;
/**
* Determines whether Kibana supports multiple spaces or only the default space.
*
* When `xpack.spaces.maxSpaces` is set to 1 Kibana only supports the default space and any spaces related UI can safely be hidden.
*/
hasOnlyDefaultSpace$: Observable<boolean>;
}
export class SpacesPlugin
implements Plugin<SpacesPluginSetup, SpacesPluginStart, PluginsSetup, PluginsStart>
{
private readonly config$: Observable<ConfigType>;
private readonly log: Logger;
private readonly spacesLicenseService = new SpacesLicenseService();
private readonly spacesClientService: SpacesClientService;
private readonly spacesService: SpacesService;
private readonly hasOnlyDefaultSpace$: Observable<boolean>;
private spacesServiceStart?: SpacesServiceStart;
private defaultSpaceService?: DefaultSpaceService;
private onCloud$ = new BehaviorSubject<boolean>(false);
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config$ = combineLatest([
initializerContext.config.create<ConfigType>(),
this.onCloud$,
]).pipe(
map(
([config, onCloud]): ConfigType => ({
...config,
// We only allow "solution" to be set on cloud environments, not on prem
// unless the forceSolutionVisibility flag is set.
allowSolutionVisibility:
(onCloud && config.allowSolutionVisibility) ||
Boolean(config.experimental?.forceSolutionVisibility),
})
)
);
this.hasOnlyDefaultSpace$ = this.config$.pipe(map(({ maxSpaces }) => maxSpaces === 1));
this.log = initializerContext.logger.get();
this.spacesService = new SpacesService();
this.spacesClientService = new SpacesClientService(
(message) => this.log.debug(message),
initializerContext.env.packageInfo.buildFlavor
);
}
public setup(core: CoreSetup<PluginsStart>, plugins: PluginsSetup): SpacesPluginSetup {
this.onCloud$.next(plugins.cloud !== undefined && plugins.cloud.isCloudEnabled);
const spacesClientSetup = this.spacesClientService.setup({ config$: this.config$ });
core.uiSettings.registerGlobal(getUiSettings());
const spacesServiceSetup = this.spacesService.setup({
basePath: core.http.basePath,
});
const getSpacesService = () => {
if (!this.spacesServiceStart) {
throw new Error('spaces service has not been initialized!');
}
return this.spacesServiceStart;
};
const usageStatsServicePromise = new UsageStatsService(this.log).setup({
getStartServices: core.getStartServices,
});
const savedObjectsService = new SpacesSavedObjectsService();
savedObjectsService.setup({ core, getSpacesService });
const { license } = this.spacesLicenseService.setup({ license$: plugins.licensing.license$ });
this.defaultSpaceService = new DefaultSpaceService();
this.defaultSpaceService.setup({
coreStatus: core.status,
getSavedObjects: async () => (await core.getStartServices())[0].savedObjects,
license$: plugins.licensing.license$,
spacesLicense: license,
logger: this.log,
solution: plugins.cloud?.onboarding?.defaultSolution,
});
initSpacesViewsRoutes({
httpResources: core.http.resources,
basePath: core.http.basePath,
logger: this.log,
});
const router = core.http.createRouter<SpacesRequestHandlerContext>();
initExternalSpacesApi({
router,
log: this.log,
getStartServices: core.getStartServices,
getSpacesService,
usageStatsServicePromise,
isServerless: this.initializerContext.env.packageInfo.buildFlavor === 'serverless',
});
initInternalSpacesApi({
router,
getSpacesService,
});
initSpacesRequestInterceptors({
http: core.http,
log: this.log,
getSpacesService,
features: plugins.features,
});
setupCapabilities(core, getSpacesService, this.log);
if (plugins.usageCollection) {
const getIndexForType = (type: string) =>
core.getStartServices().then(([coreStart]) => coreStart.savedObjects.getIndexForType(type));
registerSpacesUsageCollector(plugins.usageCollection, {
getIndexForType,
features: plugins.features,
licensing: plugins.licensing,
usageStatsServicePromise,
});
}
if (plugins.home) {
plugins.home.tutorials.addScopedTutorialContextFactory(
createSpacesTutorialContextFactory(getSpacesService)
);
}
return {
spacesClient: spacesClientSetup,
spacesService: spacesServiceSetup,
hasOnlyDefaultSpace$: this.hasOnlyDefaultSpace$,
};
}
public start(core: CoreStart, plugins: PluginsStart) {
const spacesClientStart = this.spacesClientService.start(core, plugins.features);
this.spacesServiceStart = this.spacesService.start({
basePath: core.http.basePath,
spacesClientService: spacesClientStart,
});
return {
spacesService: this.spacesServiceStart,
hasOnlyDefaultSpace$: this.hasOnlyDefaultSpace$,
};
}
public stop() {
if (this.defaultSpaceService) {
this.defaultSpaceService.stop();
}
}
}