diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.deprecations.md b/docs/development/core/public/kibana-plugin-core-public.corestart.deprecations.md new file mode 100644 index 0000000000000..624c4868d54a7 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.deprecations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [CoreStart](./kibana-plugin-core-public.corestart.md) > [deprecations](./kibana-plugin-core-public.corestart.deprecations.md) + +## CoreStart.deprecations property + +[DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) + +Signature: + +```typescript +deprecations: DeprecationsServiceStart; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.corestart.md b/docs/development/core/public/kibana-plugin-core-public.corestart.md index a7b45b318d2c9..6ad9adca53ef5 100644 --- a/docs/development/core/public/kibana-plugin-core-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.corestart.md @@ -18,6 +18,7 @@ export interface CoreStart | --- | --- | --- | | [application](./kibana-plugin-core-public.corestart.application.md) | ApplicationStart | [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) | | [chrome](./kibana-plugin-core-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-core-public.chromestart.md) | +| [deprecations](./kibana-plugin-core-public.corestart.deprecations.md) | DeprecationsServiceStart | [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) | | [docLinks](./kibana-plugin-core-public.corestart.doclinks.md) | DocLinksStart | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | | [fatalErrors](./kibana-plugin-core-public.corestart.fatalerrors.md) | FatalErrorsStart | [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) | | [http](./kibana-plugin-core-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-core-public.httpstart.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md new file mode 100644 index 0000000000000..8175da8a1893a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) > [getAllDeprecations](./kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md) + +## DeprecationsServiceStart.getAllDeprecations property + +Grabs deprecations details for all domains. + +Signature: + +```typescript +getAllDeprecations: () => Promise; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md new file mode 100644 index 0000000000000..6e3472b7c3fe3 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) > [getDeprecations](./kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md) + +## DeprecationsServiceStart.getDeprecations property + +Grabs deprecations for a specific domain. + +Signature: + +```typescript +getDeprecations: (domainId: string) => Promise; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md new file mode 100644 index 0000000000000..842761f6b7cea --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) > [isDeprecationResolvable](./kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md) + +## DeprecationsServiceStart.isDeprecationResolvable property + +Returns a boolean if the provided deprecation can be automatically resolvable. + +Signature: + +```typescript +isDeprecationResolvable: (details: DomainDeprecationDetails) => boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.md new file mode 100644 index 0000000000000..0d2c963ec5547 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) + +## DeprecationsServiceStart interface + +DeprecationsService provides methods to fetch domain deprecation details from the Kibana server. + +Signature: + +```typescript +export interface DeprecationsServiceStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getAllDeprecations](./kibana-plugin-core-public.deprecationsservicestart.getalldeprecations.md) | () => Promise<DomainDeprecationDetails[]> | Grabs deprecations details for all domains. | +| [getDeprecations](./kibana-plugin-core-public.deprecationsservicestart.getdeprecations.md) | (domainId: string) => Promise<DomainDeprecationDetails[]> | Grabs deprecations for a specific domain. | +| [isDeprecationResolvable](./kibana-plugin-core-public.deprecationsservicestart.isdeprecationresolvable.md) | (details: DomainDeprecationDetails) => boolean | Returns a boolean if the provided deprecation can be automatically resolvable. | +| [resolveDeprecation](./kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md) | (details: DomainDeprecationDetails) => Promise<ResolveDeprecationResponse> | Calls the correctiveActions.api to automatically resolve the depprecation. | + diff --git a/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md new file mode 100644 index 0000000000000..fae623fed3cc2 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) > [resolveDeprecation](./kibana-plugin-core-public.deprecationsservicestart.resolvedeprecation.md) + +## DeprecationsServiceStart.resolveDeprecation property + +Calls the correctiveActions.api to automatically resolve the depprecation. + +Signature: + +```typescript +resolveDeprecation: (details: DomainDeprecationDetails) => Promise; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index e9d08dcd3bf4c..32f17d5488f66 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -59,6 +59,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ChromeUserBanner](./kibana-plugin-core-public.chromeuserbanner.md) | | | [CoreSetup](./kibana-plugin-core-public.coresetup.md) | Core services exposed to the Plugin setup lifecycle | | [CoreStart](./kibana-plugin-core-public.corestart.md) | Core services exposed to the Plugin start lifecycle | +| [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) | DeprecationsService provides methods to fetch domain deprecation details from the Kibana server. | | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | | | [ErrorToastOptions](./kibana-plugin-core-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-core-public.itoasts.md) error APIs. | | [FatalErrorInfo](./kibana-plugin-core-public.fatalerrorinfo.md) | Represents the message and stack of a fatal Error | @@ -164,6 +165,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PublicAppMetaInfo](./kibana-plugin-core-public.publicappmetainfo.md) | Public information about a registered app's [keywords](./kibana-plugin-core-public.appmeta.md) | | [PublicAppSearchDeepLinkInfo](./kibana-plugin-core-public.publicappsearchdeeplinkinfo.md) | Public information about a registered app's [searchDeepLinks](./kibana-plugin-core-public.appsearchdeeplink.md) | | [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | +| [ResolveDeprecationResponse](./kibana-plugin-core-public.resolvedeprecationresponse.md) | | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-core-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-core-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-core-public.savedobjectsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.resolvedeprecationresponse.md b/docs/development/core/public/kibana-plugin-core-public.resolvedeprecationresponse.md new file mode 100644 index 0000000000000..928bf8c07004e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.resolvedeprecationresponse.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolveDeprecationResponse](./kibana-plugin-core-public.resolvedeprecationresponse.md) + +## ResolveDeprecationResponse type + +Signature: + +```typescript +export declare type ResolveDeprecationResponse = { + status: 'ok'; +} | { + status: 'fail'; + reason: string; +}; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.deprecations.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.deprecations.md new file mode 100644 index 0000000000000..436cc29b6e343 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.deprecations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [deprecations](./kibana-plugin-core-server.coresetup.deprecations.md) + +## CoreSetup.deprecations property + +[DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) + +Signature: + +```typescript +deprecations: DeprecationsServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index 1171dbad570ce..b37ac80db87d6 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -18,6 +18,7 @@ export interface CoreSetupCapabilitiesSetup | [CapabilitiesSetup](./kibana-plugin-core-server.capabilitiessetup.md) | | [context](./kibana-plugin-core-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | +| [deprecations](./kibana-plugin-core-server.coresetup.deprecations.md) | DeprecationsServiceSetup | [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) | | [elasticsearch](./kibana-plugin-core-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart, TStart> | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | | [http](./kibana-plugin-core-server.coresetup.http.md) | HttpServiceSetup & {
resources: HttpResources;
} | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md new file mode 100644 index 0000000000000..e362bc4e0329c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.correctiveactions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [correctiveActions](./kibana-plugin-core-server.deprecationsdetails.correctiveactions.md) + +## DeprecationsDetails.correctiveActions property + +Signature: + +```typescript +correctiveActions: { + api?: { + path: string; + method: 'POST' | 'PUT'; + body?: { + [key: string]: any; + }; + }; + manualSteps?: string[]; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.documentationurl.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.documentationurl.md new file mode 100644 index 0000000000000..467d6d76cf842 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.documentationurl.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [documentationUrl](./kibana-plugin-core-server.deprecationsdetails.documentationurl.md) + +## DeprecationsDetails.documentationUrl property + +Signature: + +```typescript +documentationUrl?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.level.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.level.md new file mode 100644 index 0000000000000..64ad22e2c87fb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.level.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [level](./kibana-plugin-core-server.deprecationsdetails.level.md) + +## DeprecationsDetails.level property + +levels: - warning: will not break deployment upon upgrade - critical: needs to be addressed before upgrade. - fetch\_error: Deprecations service failed to grab the deprecation details for the domain. + +Signature: + +```typescript +level: 'warning' | 'critical' | 'fetch_error'; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md new file mode 100644 index 0000000000000..bb77e4247711f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) + +## DeprecationsDetails interface + +Signature: + +```typescript +export interface DeprecationsDetails +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [correctiveActions](./kibana-plugin-core-server.deprecationsdetails.correctiveactions.md) | {
api?: {
path: string;
method: 'POST' | 'PUT';
body?: {
[key: string]: any;
};
};
manualSteps?: string[];
} | | +| [documentationUrl](./kibana-plugin-core-server.deprecationsdetails.documentationurl.md) | string | | +| [level](./kibana-plugin-core-server.deprecationsdetails.level.md) | 'warning' | 'critical' | 'fetch_error' | levels: - warning: will not break deployment upon upgrade - critical: needs to be addressed before upgrade. - fetch\_error: Deprecations service failed to grab the deprecation details for the domain. | +| [message](./kibana-plugin-core-server.deprecationsdetails.message.md) | string | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md new file mode 100644 index 0000000000000..d79a4c9bd7995 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.message.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [message](./kibana-plugin-core-server.deprecationsdetails.message.md) + +## DeprecationsDetails.message property + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md new file mode 100644 index 0000000000000..7d9d3dcdda4da --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md @@ -0,0 +1,95 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) + +## DeprecationsServiceSetup interface + +The deprecations service provides a way for the Kibana platform to communicate deprecated features and configs with its users. These deprecations are only communicated if the deployment is using these features. Allowing for a user tailored experience for upgrading the stack version. + +The Deprecation service is consumed by the upgrade assistant to assist with the upgrade experience. + +If a deprecated feature can be resolved without manual user intervention. Using correctiveActions.api allows the Upgrade Assistant to use this api to correct the deprecation upon a user trigger. + +Signature: + +```typescript +export interface DeprecationsServiceSetup +``` + +## Example + + +```ts +import { DeprecationsDetails, GetDeprecationsContext, CoreSetup } from 'src/core/server'; + +async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise { + const deprecations: DeprecationsDetails[] = []; + const count = await getTimelionSheetsCount(savedObjectsClient); + + if (count > 0) { + // Example of a manual correctiveAction + deprecations.push({ + message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.`, + documentationUrl: + 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', + level: 'warning', + correctiveActions: { + manualSteps: [ + 'Navigate to the Kibana Dashboard and click "Create dashboard".', + 'Select Timelion from the "New Visualization" window.', + 'Open a new tab, open the Timelion app, select the chart you want to copy, then copy the chart expression.', + 'Go to Timelion, paste the chart expression in the Timelion expression field, then click Update.', + 'In the toolbar, click Save.', + 'On the Save visualization window, enter the visualization Title, then click Save and return.', + ], + }, + }); + } + + // Example of an api correctiveAction + deprecations.push({ + "message": "User 'test_dashboard_user' is using a deprecated role: 'kibana_user'", + "documentationUrl": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html", + "level": "critical", + "correctiveActions": { + "api": { + "path": "/internal/security/users/test_dashboard_user", + "method": "POST", + "body": { + "username": "test_dashboard_user", + "roles": [ + "machine_learning_user", + "enrich_user", + "kibana_admin" + ], + "full_name": "Alison Goryachev", + "email": "alisongoryachev@gmail.com", + "metadata": {}, + "enabled": true + } + }, + "manualSteps": [ + "Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.", + "Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role." + ] + }, + }); + + return deprecations; +} + + +export class Plugin() { + setup: (core: CoreSetup) => { + core.deprecations.registerDeprecations({ getDeprecations }); + } +} + +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [registerDeprecations](./kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md) | (deprecationContext: RegisterDeprecationsConfig) => void | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md new file mode 100644 index 0000000000000..07c2a3ad0ce55 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) > [registerDeprecations](./kibana-plugin-core-server.deprecationsservicesetup.registerdeprecations.md) + +## DeprecationsServiceSetup.registerDeprecations property + +Signature: + +```typescript +registerDeprecations: (deprecationContext: RegisterDeprecationsConfig) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.esclient.md b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.esclient.md new file mode 100644 index 0000000000000..70c1864bf905f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.esclient.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) > [esClient](./kibana-plugin-core-server.getdeprecationscontext.esclient.md) + +## GetDeprecationsContext.esClient property + +Signature: + +```typescript +esClient: IScopedClusterClient; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.md b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.md new file mode 100644 index 0000000000000..1018444f0849a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) + +## GetDeprecationsContext interface + +Signature: + +```typescript +export interface GetDeprecationsContext +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [esClient](./kibana-plugin-core-server.getdeprecationscontext.esclient.md) | IScopedClusterClient | | +| [savedObjectsClient](./kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md) | SavedObjectsClientContract | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md new file mode 100644 index 0000000000000..66da52d3b5824 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) > [savedObjectsClient](./kibana-plugin-core-server.getdeprecationscontext.savedobjectsclient.md) + +## GetDeprecationsContext.savedObjectsClient property + +Signature: + +```typescript +savedObjectsClient: SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 4bf00d2da6e23..faac8108de825 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -69,13 +69,16 @@ The plugin integrates with the core system via lifecycle events: `setup` | [DeprecationAPIClientParams](./kibana-plugin-core-server.deprecationapiclientparams.md) | | | [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) | | | [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) | | +| [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) | | | [DeprecationSettings](./kibana-plugin-core-server.deprecationsettings.md) | UiSettings deprecation field options. | +| [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) | The deprecations service provides a way for the Kibana platform to communicate deprecated features and configs with its users. These deprecations are only communicated if the deployment is using these features. Allowing for a user tailored experience for upgrading the stack version.The Deprecation service is consumed by the upgrade assistant to assist with the upgrade experience.If a deprecated feature can be resolved without manual user intervention. Using correctiveActions.api allows the Upgrade Assistant to use this api to correct the deprecation upon a user trigger. | | [DiscoveredPlugin](./kibana-plugin-core-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | | | [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | | | [ErrorHttpResponseOptions](./kibana-plugin-core-server.errorhttpresponseoptions.md) | HTTP response parameters | | [FakeRequest](./kibana-plugin-core-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | +| [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) | | | [GetResponse](./kibana-plugin-core-server.getresponse.md) | | | [HttpAuth](./kibana-plugin-core-server.httpauth.md) | | | [HttpResources](./kibana-plugin-core-server.httpresources.md) | HttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP. Provides API allowing plug-ins to respond with: - a pre-configured HTML page bootstrapping Kibana client app - custom HTML page - custom JS script file. | @@ -128,6 +131,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginConfigDescriptor](./kibana-plugin-core-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. | | [PluginInitializerContext](./kibana-plugin-core-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | | [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | +| [RegisterDeprecationsConfig](./kibana-plugin-core-server.registerdeprecationsconfig.md) | | | [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.client](./kibana-plugin-core-server.iscopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.legacy.client](./kibana-plugin-core-server.legacyscopedclusterclient.md) - The legacy Elasticsearch data client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | | [ResolveCapabilitiesOptions](./kibana-plugin-core-server.resolvecapabilitiesoptions.md) | Defines a set of additional options for the resolveCapabilities method of [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md). | | [RouteConfig](./kibana-plugin-core-server.routeconfig.md) | Route specific configuration. | diff --git a/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md b/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md new file mode 100644 index 0000000000000..cf008725ff15b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [RegisterDeprecationsConfig](./kibana-plugin-core-server.registerdeprecationsconfig.md) > [getDeprecations](./kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md) + +## RegisterDeprecationsConfig.getDeprecations property + +Signature: + +```typescript +getDeprecations: (context: GetDeprecationsContext) => MaybePromise; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.md b/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.md new file mode 100644 index 0000000000000..59e6d406f84bf --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.registerdeprecationsconfig.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [RegisterDeprecationsConfig](./kibana-plugin-core-server.registerdeprecationsconfig.md) + +## RegisterDeprecationsConfig interface + +Signature: + +```typescript +export interface RegisterDeprecationsConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getDeprecations](./kibana-plugin-core-server.registerdeprecationsconfig.getdeprecations.md) | (context: GetDeprecationsContext) => MaybePromise<DeprecationsDetails[]> | | + diff --git a/packages/kbn-config/src/config_service.mock.ts b/packages/kbn-config/src/config_service.mock.ts index 638627caf1e50..83fbf20b5c0b3 100644 --- a/packages/kbn-config/src/config_service.mock.ts +++ b/packages/kbn-config/src/config_service.mock.ts @@ -25,13 +25,16 @@ const createConfigServiceMock = ({ setSchema: jest.fn(), addDeprecationProvider: jest.fn(), validate: jest.fn(), + getHandledDeprecatedConfigs: jest.fn(), }; + mocked.atPath.mockReturnValue(new BehaviorSubject(atPath)); mocked.atPathSync.mockReturnValue(atPath); mocked.getConfig$.mockReturnValue(new BehaviorSubject(new ObjectToConfigAdapter(getConfig$))); mocked.getUsedPaths.mockResolvedValue([]); mocked.getUnusedPaths.mockResolvedValue([]); mocked.isEnabledAtPath.mockResolvedValue(true); + mocked.getHandledDeprecatedConfigs.mockReturnValue([]); return mocked; }; diff --git a/packages/kbn-config/src/config_service.test.mocks.ts b/packages/kbn-config/src/config_service.test.mocks.ts index 99539726c3e43..d8da2852b9251 100644 --- a/packages/kbn-config/src/config_service.test.mocks.ts +++ b/packages/kbn-config/src/config_service.test.mocks.ts @@ -7,9 +7,15 @@ */ export const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] }); +import type { applyDeprecations } from './deprecation/apply_deprecations'; + jest.mock('../../../package.json', () => mockPackage); -export const mockApplyDeprecations = jest.fn((config, deprecations, log) => config); +export const mockApplyDeprecations = jest.fn< + Record, + Parameters +>((config, deprecations, createAddDeprecation) => config); + jest.mock('./deprecation/apply_deprecations', () => ({ applyDeprecations: mockApplyDeprecations, })); diff --git a/packages/kbn-config/src/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts index e38fff866df89..64404341bc64d 100644 --- a/packages/kbn-config/src/config_service.test.ts +++ b/packages/kbn-config/src/config_service.test.ts @@ -72,10 +72,10 @@ test('throws if config at path does not match schema', async () => { ); await expect(valuesReceived).toMatchInlineSnapshot(` - Array [ - [Error: [config validation of [key]]: expected value of type [string] but got [number]], - ] - `); + Array [ + [Error: [config validation of [key]]: expected value of type [string] but got [number]], + ] + `); }); test('re-validate config when updated', async () => { @@ -97,11 +97,11 @@ test('re-validate config when updated', async () => { rawConfig$.next({ key: 123 }); - await expect(valuesReceived).toMatchInlineSnapshot(` - Array [ - "value", - [Error: [config validation of [key]]: expected value of type [string] but got [number]], - ] + expect(valuesReceived).toMatchInlineSnapshot(` + Array [ + "value", + [Error: [config validation of [key]]: expected value of type [string] but got [number]], + ] `); }); @@ -416,10 +416,10 @@ test('throws during validation is any schema is invalid', async () => { test('logs deprecation warning during validation', async () => { const rawConfig = getRawConfigProvider({}); const configService = new ConfigService(rawConfig, defaultEnv, logger); - - mockApplyDeprecations.mockImplementationOnce((config, deprecations, log) => { - log('some deprecation message'); - log('another deprecation message'); + mockApplyDeprecations.mockImplementationOnce((config, deprecations, createAddDeprecation) => { + const addDeprecation = createAddDeprecation!(''); + addDeprecation({ message: 'some deprecation message' }); + addDeprecation({ message: 'another deprecation message' }); return config; }); @@ -437,6 +437,37 @@ test('logs deprecation warning during validation', async () => { `); }); +test('does not log warnings for silent deprecations during validation', async () => { + const rawConfig = getRawConfigProvider({}); + const configService = new ConfigService(rawConfig, defaultEnv, logger); + + mockApplyDeprecations + .mockImplementationOnce((config, deprecations, createAddDeprecation) => { + const addDeprecation = createAddDeprecation!(''); + addDeprecation({ message: 'some deprecation message', silent: true }); + addDeprecation({ message: 'another deprecation message' }); + return config; + }) + .mockImplementationOnce((config, deprecations, createAddDeprecation) => { + const addDeprecation = createAddDeprecation!(''); + addDeprecation({ message: 'I am silent', silent: true }); + return config; + }); + + loggerMock.clear(logger); + await configService.validate(); + expect(loggerMock.collect(logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "another deprecation message", + ], + ] + `); + loggerMock.clear(logger); + await configService.validate(); + expect(loggerMock.collect(logger).warn).toMatchInlineSnapshot(`Array []`); +}); + describe('atPathSync', () => { test('returns the value at path', async () => { const rawConfig = getRawConfigProvider({ key: 'foo' }); @@ -477,3 +508,36 @@ describe('atPathSync', () => { expect(configService.atPathSync('key')).toEqual('new-value'); }); }); + +describe('getHandledDeprecatedConfigs', () => { + it('returns all handled deprecated configs', async () => { + const rawConfig = getRawConfigProvider({ base: { unused: 'unusedConfig' } }); + const configService = new ConfigService(rawConfig, defaultEnv, logger); + + configService.addDeprecationProvider('base', ({ unused }) => [unused('unused')]); + + mockApplyDeprecations.mockImplementationOnce((config, deprecations, createAddDeprecation) => { + deprecations.forEach((deprecation) => { + const addDeprecation = createAddDeprecation!(deprecation.path); + addDeprecation({ message: `some deprecation message`, documentationUrl: 'some-url' }); + }); + return config; + }); + + await configService.validate(); + + expect(configService.getHandledDeprecatedConfigs()).toMatchInlineSnapshot(` + Array [ + Array [ + "base", + Array [ + Object { + "documentationUrl": "some-url", + "message": "some deprecation message", + }, + ], + ], + ] + `); + }); +}); diff --git a/packages/kbn-config/src/config_service.ts b/packages/kbn-config/src/config_service.ts index d71327350d212..91927b4c7b5c9 100644 --- a/packages/kbn-config/src/config_service.ts +++ b/packages/kbn-config/src/config_service.ts @@ -21,6 +21,7 @@ import { ConfigDeprecationWithContext, ConfigDeprecationProvider, configDeprecationFactory, + DeprecatedConfigDetails, } from './deprecation'; import { LegacyObjectToConfigAdapter } from './legacy'; @@ -43,6 +44,7 @@ export class ConfigService { private readonly handledPaths: Set = new Set(); private readonly schemas = new Map>(); private readonly deprecations = new BehaviorSubject([]); + private readonly handledDeprecatedConfigs = new Map(); constructor( private readonly rawConfigProvider: RawConfigurationProvider, @@ -91,6 +93,13 @@ export class ConfigService { ]); } + /** + * returns all handled deprecated configs + */ + public getHandledDeprecatedConfigs() { + return [...this.handledDeprecatedConfigs.entries()]; + } + /** * Validate the whole configuration and log the deprecation warnings. * @@ -186,8 +195,16 @@ export class ConfigService { const rawConfig = await this.rawConfigProvider.getConfig$().pipe(take(1)).toPromise(); const deprecations = await this.deprecations.pipe(take(1)).toPromise(); const deprecationMessages: string[] = []; - const logger = (msg: string) => deprecationMessages.push(msg); - applyDeprecations(rawConfig, deprecations, logger); + const createAddDeprecation = (domainId: string) => (context: DeprecatedConfigDetails) => { + if (!context.silent) { + deprecationMessages.push(context.message); + } + const handledDeprecatedConfig = this.handledDeprecatedConfigs.get(domainId) || []; + handledDeprecatedConfig.push(context); + this.handledDeprecatedConfigs.set(domainId, handledDeprecatedConfig); + }; + + applyDeprecations(rawConfig, deprecations, createAddDeprecation); deprecationMessages.forEach((msg) => { this.deprecationLog.warn(msg); }); diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts index 9e058faf68052..f2c0a43916343 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts @@ -32,8 +32,31 @@ describe('applyDeprecations', () => { expect(handlerC).toHaveBeenCalledTimes(1); }); + it('passes path to addDeprecation factory', () => { + const addDeprecation = jest.fn(); + const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation); + const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; + const alteredConfig = { foo: 'bar' }; + + const handlerA = jest.fn().mockReturnValue(alteredConfig); + const handlerB = jest.fn().mockImplementation((conf) => conf); + + applyDeprecations( + initialConfig, + [wrapHandler(handlerA, 'pathA'), wrapHandler(handlerB, 'pathB')], + createAddDeprecation + ); + + expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', addDeprecation); + expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', addDeprecation); + expect(createAddDeprecation).toBeCalledTimes(2); + expect(createAddDeprecation).toHaveBeenNthCalledWith(1, 'pathA'); + expect(createAddDeprecation).toHaveBeenNthCalledWith(2, 'pathB'); + }); + it('calls handlers with correct arguments', () => { - const logger = () => undefined; + const addDeprecation = jest.fn(); + const createAddDeprecation = jest.fn().mockReturnValue(addDeprecation); const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; const alteredConfig = { foo: 'bar' }; @@ -43,11 +66,11 @@ describe('applyDeprecations', () => { applyDeprecations( initialConfig, [wrapHandler(handlerA, 'pathA'), wrapHandler(handlerB, 'pathB')], - logger + createAddDeprecation ); - expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', logger); - expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', logger); + expect(handlerA).toHaveBeenCalledWith(initialConfig, 'pathA', addDeprecation); + expect(handlerB).toHaveBeenCalledWith(alteredConfig, 'pathB', addDeprecation); }); it('returns the migrated config', () => { diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.ts b/packages/kbn-config/src/deprecation/apply_deprecations.ts index 0813440adb57c..6aced541dc30d 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.ts @@ -7,23 +7,24 @@ */ import { cloneDeep } from 'lodash'; -import { ConfigDeprecationWithContext, ConfigDeprecationLogger } from './types'; - -const noopLogger = (msg: string) => undefined; +import { ConfigDeprecationWithContext, AddConfigDeprecation } from './types'; +const noopAddDeprecationFactory: () => AddConfigDeprecation = () => () => undefined; /** - * Applies deprecations on given configuration and logs any deprecation warning using provided logger. + * Applies deprecations on given configuration and passes addDeprecation hook. + * This hook is used for logging any deprecation warning using provided logger. + * This hook is used for exposing deprecated configs that must be handled by the user before upgrading to next major. * * @internal */ export const applyDeprecations = ( config: Record, deprecations: ConfigDeprecationWithContext[], - logger: ConfigDeprecationLogger = noopLogger + createAddDeprecation: (pluginId: string) => AddConfigDeprecation = noopAddDeprecationFactory ) => { let processed = cloneDeep(config); deprecations.forEach(({ deprecation, path }) => { - processed = deprecation(processed, path, logger); + processed = deprecation(processed, path, createAddDeprecation(path)); }); return processed; }; diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts index ba8a0cbf7ca57..11a49ed79d170 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts @@ -6,17 +6,16 @@ * Side Public License, v 1. */ -import { ConfigDeprecationLogger } from './types'; +import { DeprecatedConfigDetails } from './types'; import { configDeprecationFactory } from './deprecation_factory'; describe('DeprecationFactory', () => { const { rename, unused, renameFromRoot, unusedFromRoot } = configDeprecationFactory; - let deprecationMessages: string[]; - const logger: ConfigDeprecationLogger = (msg) => deprecationMessages.push(msg); + const addDeprecation = jest.fn(); beforeEach(() => { - deprecationMessages = []; + addDeprecation.mockClear(); }); describe('rename', () => { @@ -30,7 +29,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', logger); + const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { renamed: 'toberenamed', @@ -40,9 +39,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Replace \\"myplugin.deprecated\\" with \\"myplugin.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).", + ], + }, + "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"", + }, + ], ] `); }); @@ -56,7 +64,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = rename('deprecated', 'new')(rawConfig, 'myplugin', logger); + const processed = rename('deprecated', 'new')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { new: 'new', @@ -66,7 +74,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages.length).toEqual(0); + expect(addDeprecation).toHaveBeenCalledTimes(0); }); it('handles nested keys', () => { const rawConfig = { @@ -83,7 +91,7 @@ describe('DeprecationFactory', () => { const processed = rename('oldsection.deprecated', 'newsection.renamed')( rawConfig, 'myplugin', - logger + addDeprecation ); expect(processed).toEqual({ myplugin: { @@ -97,9 +105,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.oldsection.deprecated\\" is deprecated and has been replaced by \\"myplugin.newsection.renamed\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Replace \\"myplugin.oldsection.deprecated\\" with \\"myplugin.newsection.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).", + ], + }, + "message": "\\"myplugin.oldsection.deprecated\\" is deprecated and has been replaced by \\"myplugin.newsection.renamed\\"", + }, + ], ] `); }); @@ -110,15 +127,25 @@ describe('DeprecationFactory', () => { renamed: 'renamed', }, }; - const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', logger); + const processed = rename('deprecated', 'renamed')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { renamed: 'renamed', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Make sure \\"myplugin.renamed\\" contains the correct value in the config file, CLI flag, or environment variable (in Docker only).", + "Remove \\"myplugin.deprecated\\" from the config.", + ], + }, + "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"", + }, + ], ] `); }); @@ -138,7 +165,7 @@ describe('DeprecationFactory', () => { const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')( rawConfig, 'does-not-matter', - logger + addDeprecation ); expect(processed).toEqual({ myplugin: { @@ -149,9 +176,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Replace \\"myplugin.deprecated\\" with \\"myplugin.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).", + ], + }, + "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\"", + }, + ], ] `); }); @@ -169,7 +205,7 @@ describe('DeprecationFactory', () => { const processed = renameFromRoot('oldplugin.deprecated', 'newplugin.renamed')( rawConfig, 'does-not-matter', - logger + addDeprecation ); expect(processed).toEqual({ oldplugin: { @@ -180,9 +216,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"oldplugin.deprecated\\" is deprecated and has been replaced by \\"newplugin.renamed\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Replace \\"oldplugin.deprecated\\" with \\"newplugin.renamed\\" in the Kibana config file, CLI flag, or environment variable (in Docker only).", + ], + }, + "message": "\\"oldplugin.deprecated\\" is deprecated and has been replaced by \\"newplugin.renamed\\"", + }, + ], ] `); }); @@ -200,7 +245,7 @@ describe('DeprecationFactory', () => { const processed = renameFromRoot('myplugin.deprecated', 'myplugin.new')( rawConfig, 'does-not-matter', - logger + addDeprecation ); expect(processed).toEqual({ myplugin: { @@ -211,7 +256,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages.length).toEqual(0); + expect(addDeprecation).toBeCalledTimes(0); }); it('remove the old property but does not overrides the new one if they both exist, and logs a specific message', () => { @@ -224,16 +269,27 @@ describe('DeprecationFactory', () => { const processed = renameFromRoot('myplugin.deprecated', 'myplugin.renamed')( rawConfig, 'does-not-matter', - logger + addDeprecation ); expect(processed).toEqual({ myplugin: { renamed: 'renamed', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Make sure \\"myplugin.renamed\\" contains the correct value in the config file, CLI flag, or environment variable (in Docker only).", + "Remove \\"myplugin.deprecated\\" from the config.", + ], + }, + "message": "\\"myplugin.deprecated\\" is deprecated and has been replaced by \\"myplugin.renamed\\". However both key are present, ignoring \\"myplugin.deprecated\\"", + }, + ], ] `); }); @@ -250,7 +306,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unused('deprecated')(rawConfig, 'myplugin', logger); + const processed = unused('deprecated')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -259,9 +315,18 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "myplugin.deprecated is deprecated and is no longer used", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Remove \\"myplugin.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only)", + ], + }, + "message": "myplugin.deprecated is deprecated and is no longer used", + }, + ], ] `); }); @@ -278,7 +343,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unused('section.deprecated')(rawConfig, 'myplugin', logger); + const processed = unused('section.deprecated')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -288,9 +353,19 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "myplugin.section.deprecated is deprecated and is no longer used", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Remove \\"myplugin.section.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only)", + ], + }, + "message": "myplugin.section.deprecated is deprecated and is no longer used", + }, + ], ] `); }); @@ -304,7 +379,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unused('deprecated')(rawConfig, 'myplugin', logger); + const processed = unused('deprecated')(rawConfig, 'myplugin', addDeprecation); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -313,7 +388,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages.length).toEqual(0); + expect(addDeprecation).toBeCalledTimes(0); }); }); @@ -328,7 +403,11 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unusedFromRoot('myplugin.deprecated')(rawConfig, 'does-not-matter', logger); + const processed = unusedFromRoot('myplugin.deprecated')( + rawConfig, + 'does-not-matter', + addDeprecation + ); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -337,9 +416,19 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages).toMatchInlineSnapshot(` + + expect(addDeprecation.mock.calls).toMatchInlineSnapshot(` Array [ - "myplugin.deprecated is deprecated and is no longer used", + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Remove \\"myplugin.deprecated\\" from the Kibana config file, CLI flag, or environment variable (in Docker only)", + ], + }, + "message": "myplugin.deprecated is deprecated and is no longer used", + }, + ], ] `); }); @@ -353,7 +442,11 @@ describe('DeprecationFactory', () => { property: 'value', }, }; - const processed = unusedFromRoot('myplugin.deprecated')(rawConfig, 'does-not-matter', logger); + const processed = unusedFromRoot('myplugin.deprecated')( + rawConfig, + 'does-not-matter', + addDeprecation + ); expect(processed).toEqual({ myplugin: { valid: 'valid', @@ -362,7 +455,7 @@ describe('DeprecationFactory', () => { property: 'value', }, }); - expect(deprecationMessages.length).toEqual(0); + expect(addDeprecation).toBeCalledTimes(0); }); }); }); diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts index 73196dc897a51..140846d86ae0b 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.ts @@ -9,15 +9,20 @@ import { get } from 'lodash'; import { set } from '@elastic/safer-lodash-set'; import { unset } from '@kbn/std'; -import { ConfigDeprecation, ConfigDeprecationLogger, ConfigDeprecationFactory } from './types'; +import { + ConfigDeprecation, + AddConfigDeprecation, + ConfigDeprecationFactory, + DeprecatedConfigDetails, +} from './types'; const _rename = ( config: Record, rootPath: string, - log: ConfigDeprecationLogger, + addDeprecation: AddConfigDeprecation, oldKey: string, newKey: string, - silent?: boolean + details?: Partial ) => { const fullOldPath = getPath(rootPath, oldKey); const oldValue = get(config, fullOldPath); @@ -32,48 +37,80 @@ const _rename = ( if (newValue === undefined) { set(config, fullNewPath, oldValue); - if (!silent) { - log(`"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`); - } + addDeprecation({ + message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`, + correctiveActions: { + manualSteps: [ + `Replace "${fullOldPath}" with "${fullNewPath}" in the Kibana config file, CLI flag, or environment variable (in Docker only).`, + ], + }, + ...details, + }); } else { - if (!silent) { - log( - `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"` - ); - } + addDeprecation({ + message: `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"`, + correctiveActions: { + manualSteps: [ + `Make sure "${fullNewPath}" contains the correct value in the config file, CLI flag, or environment variable (in Docker only).`, + `Remove "${fullOldPath}" from the config.`, + ], + }, + ...details, + }); } + return config; }; const _unused = ( config: Record, rootPath: string, - log: ConfigDeprecationLogger, - unusedKey: string + addDeprecation: AddConfigDeprecation, + unusedKey: string, + details?: Partial ) => { const fullPath = getPath(rootPath, unusedKey); if (get(config, fullPath) === undefined) { return config; } unset(config, fullPath); - log(`${fullPath} is deprecated and is no longer used`); + addDeprecation({ + message: `${fullPath} is deprecated and is no longer used`, + correctiveActions: { + manualSteps: [ + `Remove "${fullPath}" from the Kibana config file, CLI flag, or environment variable (in Docker only)`, + ], + }, + ...details, + }); return config; }; -const rename = (oldKey: string, newKey: string): ConfigDeprecation => (config, rootPath, log) => - _rename(config, rootPath, log, oldKey, newKey); +const rename = ( + oldKey: string, + newKey: string, + details?: Partial +): ConfigDeprecation => (config, rootPath, addDeprecation) => + _rename(config, rootPath, addDeprecation, oldKey, newKey, details); -const renameFromRoot = (oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation => ( - config, - rootPath, - log -) => _rename(config, '', log, oldKey, newKey, silent); +const renameFromRoot = ( + oldKey: string, + newKey: string, + details?: Partial +): ConfigDeprecation => (config, rootPath, addDeprecation) => + _rename(config, '', addDeprecation, oldKey, newKey, details); -const unused = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) => - _unused(config, rootPath, log, unusedKey); +const unused = ( + unusedKey: string, + details?: Partial +): ConfigDeprecation => (config, rootPath, addDeprecation) => + _unused(config, rootPath, addDeprecation, unusedKey, details); -const unusedFromRoot = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) => - _unused(config, '', log, unusedKey); +const unusedFromRoot = ( + unusedKey: string, + details?: Partial +): ConfigDeprecation => (config, rootPath, addDeprecation) => + _unused(config, '', addDeprecation, unusedKey, details); const getPath = (rootPath: string, subPath: string) => rootPath !== '' ? `${rootPath}.${subPath}` : subPath; diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts index 6fe1a53efecbc..3286acca9e584 100644 --- a/packages/kbn-config/src/deprecation/index.ts +++ b/packages/kbn-config/src/deprecation/index.ts @@ -6,12 +6,13 @@ * Side Public License, v 1. */ -export { +export type { ConfigDeprecation, ConfigDeprecationWithContext, - ConfigDeprecationLogger, ConfigDeprecationFactory, + AddConfigDeprecation, ConfigDeprecationProvider, + DeprecatedConfigDetails, } from './types'; export { configDeprecationFactory } from './deprecation_factory'; export { applyDeprecations } from './apply_deprecations'; diff --git a/packages/kbn-config/src/deprecation/types.ts b/packages/kbn-config/src/deprecation/types.ts index 6e1816867abcf..3b1d004d7ec76 100644 --- a/packages/kbn-config/src/deprecation/types.ts +++ b/packages/kbn-config/src/deprecation/types.ts @@ -7,11 +7,33 @@ */ /** - * Logger interface used when invoking a {@link ConfigDeprecation} + * Config deprecation hook used when invoking a {@link ConfigDeprecation} * * @public */ -export type ConfigDeprecationLogger = (message: string) => void; +export type AddConfigDeprecation = (details: DeprecatedConfigDetails) => void; + +/** + * Deprecated Config Details + * + * @public + */ +export interface DeprecatedConfigDetails { + /* The message to be displayed for the deprecation. */ + message: string; + /* (optional) set false to prevent the config service from logging the deprecation message. */ + silent?: boolean; + /* (optional) link to the documentation for more details on the deprecation. */ + documentationUrl?: string; + /* (optional) corrective action needed to fix this deprecation. */ + correctiveActions?: { + /** + * Specify a list of manual steps our users need to follow + * to fix the deprecation before upgrade. + */ + manualSteps: string[]; + }; +} /** * Configuration deprecation returned from {@link ConfigDeprecationProvider} that handles a single deprecation from the configuration. @@ -25,7 +47,7 @@ export type ConfigDeprecationLogger = (message: string) => void; export type ConfigDeprecation = ( config: Record, fromPath: string, - logger: ConfigDeprecationLogger + addDeprecation: AddConfigDeprecation ) => Record; /** @@ -62,6 +84,7 @@ export type ConfigDeprecationProvider = (factory: ConfigDeprecationFactory) => C * * @public */ + export interface ConfigDeprecationFactory { /** * Rename a configuration property from inside a plugin's configuration path. @@ -75,7 +98,11 @@ export interface ConfigDeprecationFactory { * ] * ``` */ - rename(oldKey: string, newKey: string): ConfigDeprecation; + rename( + oldKey: string, + newKey: string, + details?: Partial + ): ConfigDeprecation; /** * Rename a configuration property from the root configuration. * Will log a deprecation warning if the oldKey was found and deprecation applied. @@ -91,7 +118,11 @@ export interface ConfigDeprecationFactory { * ] * ``` */ - renameFromRoot(oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation; + renameFromRoot( + oldKey: string, + newKey: string, + details?: Partial + ): ConfigDeprecation; /** * Remove a configuration property from inside a plugin's configuration path. * Will log a deprecation warning if the unused key was found and deprecation applied. @@ -104,7 +135,7 @@ export interface ConfigDeprecationFactory { * ] * ``` */ - unused(unusedKey: string): ConfigDeprecation; + unused(unusedKey: string, details?: Partial): ConfigDeprecation; /** * Remove a configuration property from the root configuration. * Will log a deprecation warning if the unused key was found and deprecation applied. @@ -120,7 +151,7 @@ export interface ConfigDeprecationFactory { * ] * ``` */ - unusedFromRoot(unusedKey: string): ConfigDeprecation; + unusedFromRoot(unusedKey: string, details?: Partial): ConfigDeprecation; } /** @internal */ diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index 8b0bdb0befbfd..a9ea8265a3768 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -6,16 +6,16 @@ * Side Public License, v 1. */ -export { - applyDeprecations, - ConfigDeprecation, +export type { ConfigDeprecationFactory, - configDeprecationFactory, - ConfigDeprecationLogger, + AddConfigDeprecation, ConfigDeprecationProvider, ConfigDeprecationWithContext, + ConfigDeprecation, } from './deprecation'; +export { applyDeprecations, configDeprecationFactory } from './deprecation'; + export { RawConfigurationProvider, RawConfigService, diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 278bbe469e862..b68a7ced118d2 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -28,6 +28,7 @@ import { DocLinksService } from './doc_links'; import { RenderingService } from './rendering'; import { SavedObjectsService } from './saved_objects'; import { IntegrationsService } from './integrations'; +import { DeprecationsService } from './deprecations'; import { CoreApp } from './core_app'; import type { InternalApplicationSetup, InternalApplicationStart } from './application/types'; @@ -82,7 +83,7 @@ export class CoreSystem { private readonly rendering: RenderingService; private readonly integrations: IntegrationsService; private readonly coreApp: CoreApp; - + private readonly deprecations: DeprecationsService; private readonly rootDomElement: HTMLElement; private readonly coreContext: CoreContext; private fatalErrorsSetup: FatalErrorsSetup | null = null; @@ -113,6 +114,7 @@ export class CoreSystem { this.rendering = new RenderingService(); this.application = new ApplicationService(); this.integrations = new IntegrationsService(); + this.deprecations = new DeprecationsService(); this.coreContext = { coreId: Symbol('core'), env: injectedMetadata.env }; this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins); @@ -195,6 +197,7 @@ export class CoreSystem { injectedMetadata, notifications, }); + const deprecations = this.deprecations.start({ http }); this.coreApp.start({ application, http, notifications, uiSettings }); @@ -210,6 +213,7 @@ export class CoreSystem { overlays, uiSettings, fatalErrors, + deprecations, }; await this.plugins.start(core); @@ -252,6 +256,7 @@ export class CoreSystem { this.chrome.stop(); this.i18n.stop(); this.application.stop(); + this.deprecations.stop(); this.rootDomElement.textContent = ''; } } diff --git a/src/core/public/deprecations/deprecations_client.test.ts b/src/core/public/deprecations/deprecations_client.test.ts new file mode 100644 index 0000000000000..2f52f7b4af195 --- /dev/null +++ b/src/core/public/deprecations/deprecations_client.test.ts @@ -0,0 +1,187 @@ +/* + * 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 { httpServiceMock } from '../http/http_service.mock'; +import { DeprecationsClient } from './deprecations_client'; +import type { DomainDeprecationDetails } from '../../server/types'; + +describe('DeprecationsClient', () => { + const http = httpServiceMock.createSetupContract(); + const mockDeprecations = [ + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-2' }, + ]; + + beforeEach(() => { + http.fetch.mockReset(); + http.fetch.mockResolvedValue({ deprecations: mockDeprecations }); + }); + + describe('getAllDeprecations', () => { + it('returns a list of deprecations', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const deprecations = await deprecationsClient.getAllDeprecations(); + expect(http.fetch).toBeCalledTimes(1); + expect(http.fetch).toBeCalledWith('/api/deprecations/', { + asSystemRequest: true, + }); + + expect(deprecations).toEqual(mockDeprecations); + }); + }); + + describe('getDeprecations', () => { + it('returns deprecations for a single domainId', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const deprecations = await deprecationsClient.getDeprecations('testPluginId-1'); + + expect(deprecations.length).toBe(2); + expect(deprecations).toEqual([ + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-1' }, + ]); + }); + + it('returns [] if the domainId does not have any deprecations', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const deprecations = await deprecationsClient.getDeprecations('testPluginId-4'); + + expect(deprecations).toEqual([]); + }); + + it('calls the fetch api', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + http.fetch.mockResolvedValueOnce({ + deprecations: [{ domainId: 'testPluginId-1' }, { domainId: 'testPluginId-1' }], + }); + http.fetch.mockResolvedValueOnce({ + deprecations: [{ domainId: 'testPluginId-2' }, { domainId: 'testPluginId-2' }], + }); + const results = [ + ...(await deprecationsClient.getDeprecations('testPluginId-1')), + ...(await deprecationsClient.getDeprecations('testPluginId-2')), + ]; + + expect(http.fetch).toBeCalledTimes(2); + expect(results).toEqual([ + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-1' }, + { domainId: 'testPluginId-2' }, + { domainId: 'testPluginId-2' }, + ]); + }); + }); + + describe('isDeprecationResolvable', () => { + it('returns true if deprecation has correctiveActions.api', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: { + api: { + path: 'some-path', + method: 'POST', + }, + }, + }; + + const isResolvable = deprecationsClient.isDeprecationResolvable(mockDeprecationDetails); + + expect(isResolvable).toBe(true); + }); + + it('returns false if deprecation is missing correctiveActions.api', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: {}, + }; + + const isResolvable = deprecationsClient.isDeprecationResolvable(mockDeprecationDetails); + + expect(isResolvable).toBe(false); + }); + }); + + describe('resolveDeprecation', () => { + it('fails if deprecation is not resolvable', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: {}, + }; + const result = await deprecationsClient.resolveDeprecation(mockDeprecationDetails); + + expect(result).toEqual({ + status: 'fail', + reason: 'deprecation has no correctiveAction via api.', + }); + }); + + it('fetches the deprecation api', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: { + api: { + path: 'some-path', + method: 'POST', + body: { + extra_param: 123, + }, + }, + }, + }; + const result = await deprecationsClient.resolveDeprecation(mockDeprecationDetails); + + expect(http.fetch).toBeCalledTimes(1); + expect(http.fetch).toBeCalledWith({ + path: 'some-path', + method: 'POST', + asSystemRequest: true, + body: JSON.stringify({ + extra_param: 123, + deprecationDetails: { domainId: 'testPluginId-1' }, + }), + }); + expect(result).toEqual({ status: 'ok' }); + }); + + it('fails when fetch fails', async () => { + const deprecationsClient = new DeprecationsClient({ http }); + const mockResponse = 'Failed to fetch'; + const mockDeprecationDetails: DomainDeprecationDetails = { + domainId: 'testPluginId-1', + message: 'some-message', + level: 'warning', + correctiveActions: { + api: { + path: 'some-path', + method: 'POST', + body: { + extra_param: 123, + }, + }, + }, + }; + http.fetch.mockRejectedValue({ body: { message: mockResponse } }); + const result = await deprecationsClient.resolveDeprecation(mockDeprecationDetails); + + expect(result).toEqual({ status: 'fail', reason: mockResponse }); + }); + }); +}); diff --git a/src/core/public/deprecations/deprecations_client.ts b/src/core/public/deprecations/deprecations_client.ts new file mode 100644 index 0000000000000..e510ab1e79d17 --- /dev/null +++ b/src/core/public/deprecations/deprecations_client.ts @@ -0,0 +1,78 @@ +/* + * 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 { HttpStart } from '../http'; +import type { DomainDeprecationDetails, DeprecationsGetResponse } from '../../server/types'; + +/* @internal */ +export interface DeprecationsClientDeps { + http: Pick; +} + +/* @internal */ +export type ResolveDeprecationResponse = { status: 'ok' } | { status: 'fail'; reason: string }; + +export class DeprecationsClient { + private readonly http: Pick; + constructor({ http }: DeprecationsClientDeps) { + this.http = http; + } + + private fetchDeprecations = async (): Promise => { + const { deprecations } = await this.http.fetch('/api/deprecations/', { + asSystemRequest: true, + }); + + return deprecations; + }; + + public getAllDeprecations = async () => { + return await this.fetchDeprecations(); + }; + + public getDeprecations = async (domainId: string) => { + const deprecations = await this.fetchDeprecations(); + return deprecations.filter((deprecation) => deprecation.domainId === domainId); + }; + + public isDeprecationResolvable = (details: DomainDeprecationDetails) => { + return typeof details.correctiveActions.api === 'object'; + }; + + public resolveDeprecation = async ( + details: DomainDeprecationDetails + ): Promise => { + const { domainId, correctiveActions } = details; + // explicit check required for TS type guard + if (typeof correctiveActions.api !== 'object') { + return { + status: 'fail', + reason: 'deprecation has no correctiveAction via api.', + }; + } + + const { body, method, path } = correctiveActions.api; + try { + await this.http.fetch({ + path, + method, + asSystemRequest: true, + body: JSON.stringify({ + ...body, + deprecationDetails: { domainId }, + }), + }); + return { status: 'ok' }; + } catch (err) { + return { + status: 'fail', + reason: err.body.message, + }; + } + }; +} diff --git a/src/core/public/deprecations/deprecations_service.mock.ts b/src/core/public/deprecations/deprecations_service.mock.ts new file mode 100644 index 0000000000000..5bcd52982d513 --- /dev/null +++ b/src/core/public/deprecations/deprecations_service.mock.ts @@ -0,0 +1,36 @@ +/* + * 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 { PublicMethodsOf } from '@kbn/utility-types'; +import { DeprecationsService } from './deprecations_service'; +import type { DeprecationsServiceStart } from './deprecations_service'; + +const createServiceMock = (): jest.Mocked => ({ + getAllDeprecations: jest.fn().mockResolvedValue([]), + getDeprecations: jest.fn().mockResolvedValue([]), + isDeprecationResolvable: jest.fn().mockReturnValue(false), + resolveDeprecation: jest.fn().mockResolvedValue({ status: 'ok', payload: {} }), +}); + +const createMock = () => { + const mocked: jest.Mocked> = { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; + + mocked.setup.mockReturnValue(void 0); + mocked.start.mockReturnValue(createServiceMock()); + return mocked; +}; + +export const deprecationsServiceMock = { + create: createMock, + createSetupContract: () => void 0, + createStartContract: createServiceMock, +}; diff --git a/src/core/public/deprecations/deprecations_service.ts b/src/core/public/deprecations/deprecations_service.ts new file mode 100644 index 0000000000000..d06e0071d2bc7 --- /dev/null +++ b/src/core/public/deprecations/deprecations_service.ts @@ -0,0 +1,60 @@ +/* + * 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 { CoreService } from '../../types'; +import type { HttpStart } from '../http'; +import { DeprecationsClient, ResolveDeprecationResponse } from './deprecations_client'; +import type { DomainDeprecationDetails } from '../../server/types'; + +/** + * DeprecationsService provides methods to fetch domain deprecation details from + * the Kibana server. + * + * @public + */ +export interface DeprecationsServiceStart { + /** + * Grabs deprecations details for all domains. + */ + getAllDeprecations: () => Promise; + /** + * Grabs deprecations for a specific domain. + * + * @param {string} domainId + */ + getDeprecations: (domainId: string) => Promise; + /** + * Returns a boolean if the provided deprecation can be automatically resolvable. + * + * @param {DomainDeprecationDetails} details + */ + isDeprecationResolvable: (details: DomainDeprecationDetails) => boolean; + /** + * Calls the correctiveActions.api to automatically resolve the depprecation. + * + * @param {DomainDeprecationDetails} details + */ + resolveDeprecation: (details: DomainDeprecationDetails) => Promise; +} + +export class DeprecationsService implements CoreService { + public setup(): void {} + + public start({ http }: { http: HttpStart }): DeprecationsServiceStart { + const deprecationsClient = new DeprecationsClient({ http }); + + return { + getAllDeprecations: deprecationsClient.getAllDeprecations, + getDeprecations: deprecationsClient.getDeprecations, + isDeprecationResolvable: deprecationsClient.isDeprecationResolvable, + resolveDeprecation: deprecationsClient.resolveDeprecation, + }; + } + + public stop(): void {} +} diff --git a/src/core/public/deprecations/index.ts b/src/core/public/deprecations/index.ts new file mode 100644 index 0000000000000..092cbed613ac2 --- /dev/null +++ b/src/core/public/deprecations/index.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +export { DeprecationsService } from './deprecations_service'; +export type { DeprecationsServiceStart } from './deprecations_service'; +export type { ResolveDeprecationResponse } from './deprecations_client'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index c7b4c370eb6d7..750f2e27dc950 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -65,6 +65,7 @@ import { UiSettingsState, IUiSettingsClient } from './ui_settings'; import { ApplicationSetup, Capabilities, ApplicationStart } from './application'; import { DocLinksStart } from './doc_links'; import { SavedObjectsStart } from './saved_objects'; +import { DeprecationsServiceStart } from './deprecations'; export type { PackageInfo, EnvironmentMode, IExternalUrlPolicy } from '../server/types'; export type { CoreContext, CoreSystem } from './core_system'; @@ -184,6 +185,8 @@ export type { ErrorToastOptions, } from './notifications'; +export type { DeprecationsServiceStart, ResolveDeprecationResponse } from './deprecations'; + export type { MountPoint, UnmountCallback, PublicUiSettingsParams } from './types'; export { URL_MAX_LENGTH } from './core_app'; @@ -268,6 +271,8 @@ export interface CoreStart { uiSettings: IUiSettingsClient; /** {@link FatalErrorsStart} */ fatalErrors: FatalErrorsStart; + /** {@link DeprecationsServiceStart} */ + deprecations: DeprecationsServiceStart; /** * exposed temporarily until https://github.com/elastic/kibana/issues/41990 done * use *only* to retrieve config values. There is no way to set injected values diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index e47de84ea12b2..bd7623beba651 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -24,6 +24,7 @@ import { overlayServiceMock } from './overlays/overlay_service.mock'; import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; +import { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; export { chromeServiceMock } from './chrome/chrome_service.mock'; export { docLinksServiceMock } from './doc_links/doc_links_service.mock'; @@ -37,6 +38,7 @@ export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; export { scopedHistoryMock } from './application/scoped_history.mock'; export { applicationServiceMock } from './application/application_service.mock'; +export { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; function createCoreSetupMock({ basePath = '', @@ -57,6 +59,7 @@ function createCoreSetupMock({ http: httpServiceMock.createSetupContract({ basePath }), notifications: notificationServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), + deprecations: deprecationsServiceMock.createSetupContract(), injectedMetadata: { getInjectedVar: injectedMetadataServiceMock.createSetupContract().getInjectedVar, }, @@ -76,6 +79,7 @@ function createCoreStartMock({ basePath = '' } = {}) { overlays: overlayServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createStartContract(), + deprecations: deprecationsServiceMock.createStartContract(), injectedMetadata: { getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar, }, diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index b59516fa121fb..49c895aa80fc4 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -139,5 +139,6 @@ export function createPluginStartContext< getInjectedVar: deps.injectedMetadata.getInjectedVar, }, fatalErrors: deps.fatalErrors, + deprecations: deps.deprecations, }; } diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index e70b78f237d75..d7114f14e2f00 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -34,6 +34,7 @@ import { httpServiceMock } from '../http/http_service.mock'; import { CoreSetup, CoreStart, PluginInitializerContext } from '..'; import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; +import { deprecationsServiceMock } from '../deprecations/deprecations_service.mock'; export let mockPluginInitializers: Map; @@ -101,6 +102,7 @@ describe('PluginsService', () => { uiSettings: uiSettingsServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createStartContract(), fatalErrors: fatalErrorsServiceMock.createStartContract(), + deprecations: deprecationsServiceMock.createStartContract(), }; mockStartContext = { ...mockStartDeps, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 5a5ae253bac7f..0a1c7a9b0fa36 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -432,6 +432,8 @@ export interface CoreStart { // (undocumented) chrome: ChromeStart; // (undocumented) + deprecations: DeprecationsServiceStart; + // (undocumented) docLinks: DocLinksStart; // (undocumented) fatalErrors: FatalErrorsStart; @@ -472,6 +474,15 @@ export class CoreSystem { // @internal (undocumented) export const DEFAULT_APP_CATEGORIES: Record; +// @public +export interface DeprecationsServiceStart { + // Warning: (ae-forgotten-export) The symbol "DomainDeprecationDetails" needs to be exported by the entry point index.d.ts + getAllDeprecations: () => Promise; + getDeprecations: (domainId: string) => Promise; + isDeprecationResolvable: (details: DomainDeprecationDetails) => boolean; + resolveDeprecation: (details: DomainDeprecationDetails) => Promise; +} + // @public (undocumented) export interface DocLinksStart { // (undocumented) @@ -1075,6 +1086,16 @@ export type PublicAppSearchDeepLinkInfo = Omit; +// Warning: (ae-missing-release-tag) "ResolveDeprecationResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ResolveDeprecationResponse = { + status: 'ok'; +} | { + status: 'fail'; + reason: string; +}; + // Warning: (ae-missing-release-tag) "SavedObject" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1586,6 +1607,6 @@ export interface UserProvidedValues { // Warnings were encountered during analysis: // -// src/core/public/core_system.ts:164:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts +// src/core/public/core_system.ts:166:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts ``` diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts index b6b3ab5b8face..e3c236405a596 100644 --- a/src/core/server/config/deprecation/core_deprecations.test.ts +++ b/src/core/server/config/deprecation/core_deprecations.test.ts @@ -20,7 +20,7 @@ const applyCoreDeprecations = (settings: Record = {}) => { deprecation, path: '', })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, @@ -305,7 +305,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.", + "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); @@ -315,7 +315,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.", + "\\"logging.dest\\" has been deprecated and will be removed in 8.0. To set the destination moving forward, you can use the \\"console\\" appender in your logging configuration or define a custom one. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); @@ -361,7 +361,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.json\\" has been deprecated and will be removed in 8.0. To specify log message format moving forward, you can configure the \\"appender.layout\\" property for every custom appender in your logging configuration. There is currently no default layout for custom appenders and each one must be declared explicitly. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.", + "\\"logging.json\\" has been deprecated and will be removed in 8.0. To specify log message format moving forward, you can configure the \\"appender.layout\\" property for every custom appender in your logging configuration. There is currently no default layout for custom appenders and each one must be declared explicitly. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx", ] `); }); @@ -446,7 +446,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.filter\\" has been deprecated and will be removed in 8.0. ", + "\\"logging.filter\\" has been deprecated and will be removed in 8.0.", ] `); }); @@ -457,7 +457,7 @@ describe('core deprecations', () => { }); expect(messages).toMatchInlineSnapshot(` Array [ - "\\"logging.filter\\" has been deprecated and will be removed in 8.0. ", + "\\"logging.filter\\" has been deprecated and will be removed in 8.0.", ] `); }); diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 565b957b2a8e1..2e77374e3068a 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -9,40 +9,43 @@ import { has, get } from 'lodash'; import { ConfigDeprecationProvider, ConfigDeprecation } from '@kbn/config'; -const configPathDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const configPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(process.env, 'CONFIG_PATH')) { - log( - `Environment variable CONFIG_PATH is deprecated. It has been replaced with KBN_PATH_CONF pointing to a config folder` - ); + addDeprecation({ + message: `Environment variable CONFIG_PATH is deprecated. It has been replaced with KBN_PATH_CONF pointing to a config folder`, + }); } return settings; }; -const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const dataPathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(process.env, 'DATA_PATH')) { - log( - `Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"` - ); + addDeprecation({ + message: `Environment variable "DATA_PATH" will be removed. It has been replaced with kibana.yml setting "path.data"`, + }); } return settings; }; -const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'server.basePath') && !has(settings, 'server.rewriteBasePath')) { - log( - 'You should set server.basePath along with server.rewriteBasePath. Starting in 7.0, Kibana ' + + addDeprecation({ + message: + 'You should set server.basePath along with server.rewriteBasePath. Starting in 7.0, Kibana ' + 'will expect that all requests start with server.basePath rather than expecting you to rewrite ' + 'the requests in your reverse proxy. Set server.rewriteBasePath to false to preserve the ' + - 'current behavior and silence this warning.' - ); + 'current behavior and silence this warning.', + }); } return settings; }; -const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, log) => { +const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, addDeprecation) => { const corsSettings = get(settings, 'server.cors'); if (typeof get(settings, 'server.cors') === 'boolean') { - log('"server.cors" is deprecated and has been replaced by "server.cors.enabled"'); + addDeprecation({ + message: '"server.cors" is deprecated and has been replaced by "server.cors.enabled"', + }); settings.server.cors = { enabled: corsSettings, }; @@ -50,7 +53,7 @@ const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, log) => { return settings; }; -const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { const NONCE_STRING = `{nonce}`; // Policies that should include the 'self' source const SELF_POLICIES = Object.freeze(['script-src', 'style-src']); @@ -67,7 +70,9 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { settings.csp.rules = [...parsed].map(([policy, sourceList]) => { if (sourceList.find((source) => source.includes(NONCE_STRING))) { - log(`csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`); + addDeprecation({ + message: `csp.rules no longer supports the {nonce} syntax. Replacing with 'self' in ${policy}`, + }); sourceList = sourceList.filter((source) => !source.includes(NONCE_STRING)); // Add 'self' if not present @@ -80,7 +85,9 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { SELF_POLICIES.includes(policy) && !sourceList.find((source) => source.includes(SELF_STRING)) ) { - log(`csp.rules must contain the 'self' source. Automatically adding to ${policy}.`); + addDeprecation({ + message: `csp.rules must contain the 'self' source. Automatically adding to ${policy}.`, + }); sourceList.push(SELF_STRING); } @@ -91,149 +98,191 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { return settings; }; -const mapManifestServiceUrlDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const mapManifestServiceUrlDeprecation: ConfigDeprecation = ( + settings, + fromPath, + addDeprecation +) => { if (has(settings, 'map.manifestServiceUrl')) { - log( - 'You should no longer use the map.manifestServiceUrl setting in kibana.yml to configure the location ' + + addDeprecation({ + message: + 'You should no longer use the map.manifestServiceUrl setting in kibana.yml to configure the location ' + 'of the Elastic Maps Service settings. These settings have moved to the "map.emsTileApiUrl" and ' + '"map.emsFileApiUrl" settings instead. These settings are for development use only and should not be ' + - 'modified for use in production environments.' - ); + 'modified for use in production environments.', + }); } return settings; }; -const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.events.ops')) { - log( - '"logging.events.ops" has been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', + message: + '"logging.events.ops" has been deprecated and will be removed ' + 'in 8.0. To access ops data moving forward, please enable debug logs for the ' + '"metrics.ops" context in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.events.request') || has(settings, 'logging.events.response')) { - log( - '"logging.events.request" and "logging.events.response" have been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', + message: + '"logging.events.request" and "logging.events.response" have been deprecated and will be removed ' + 'in 8.0. To access request and/or response data moving forward, please enable debug logs for the ' + '"http.server.response" context in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.timezone')) { - log( - '"logging.timezone" has been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingtimezone', + message: + '"logging.timezone" has been deprecated and will be removed ' + 'in 8.0. To set the timezone moving forward, please add a timezone date modifier to the log pattern ' + 'in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const destLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.dest')) { - log( - '"logging.dest" has been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingdest', + message: + '"logging.dest" has been deprecated and will be removed ' + 'in 8.0. To set the destination moving forward, you can use the "console" appender ' + 'in your logging configuration or define a custom one. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const quietLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.quiet')) { - log( - '"logging.quiet" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, you can use "logging.root.level:error" in your logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingquiet', + message: + '"logging.quiet" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level:error" in your logging configuration. ', + }); } return settings; }; -const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const silentLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.silent')) { - log( - '"logging.silent" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, you can use "logging.root.level:off" in your logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingsilent', + message: + '"logging.silent" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level:off" in your logging configuration. ', + }); } return settings; }; -const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const verboseLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.verbose')) { - log( - '"logging.verbose" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, you can use "logging.root.level:all" in your logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingverbose', + message: + '"logging.verbose" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level:all" in your logging configuration. ', + }); } return settings; }; -const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const jsonLoggingDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { // We silence the deprecation warning when running in development mode because // the dev CLI code in src/dev/cli_dev_mode/using_server_process.ts manually // specifies `--logging.json=false`. Since it's executed in a child process, the // ` legacyLoggingConfigSchema` returns `true` for the TTY check on `process.stdout.isTTY` if (has(settings, 'logging.json') && settings.env !== 'development') { - log( - '"logging.json" has been deprecated and will be removed ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + message: + '"logging.json" has been deprecated and will be removed ' + 'in 8.0. To specify log message format moving forward, ' + 'you can configure the "appender.layout" property for every custom appender in your logging configuration. ' + 'There is currently no default layout for custom appenders and each one must be declared explicitly. ' + 'For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx.' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx', + }); } return settings; }; -const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const logRotateDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.rotate')) { - log( - '"logging.rotate" and sub-options have been deprecated and will be removed in 8.0. ' + + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender', + message: + '"logging.rotate" and sub-options have been deprecated and will be removed in 8.0. ' + 'Moving forward, you can enable log rotation using the "rolling-file" appender for a logger ' + 'in your logging configuration. For more details, see ' + - 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender' - ); + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#rolling-file-appender', + }); } return settings; }; -const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const logEventsLogDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.events.log')) { - log( - '"logging.events.log" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', + message: + '"logging.events.log" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, log levels can be customized on a per-logger basis using the new logging configuration. ', + }); } return settings; }; -const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const logEventsErrorDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.events.error')) { - log( - '"logging.events.error" has been deprecated and will be removed ' + - 'in 8.0. Moving forward, you can use "logging.root.level: error" in your logging configuration. ' - ); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingevents', + message: + '"logging.events.error" has been deprecated and will be removed ' + + 'in 8.0. Moving forward, you can use "logging.root.level: error" in your logging configuration. ', + }); } return settings; }; -const logFilterDeprecation: ConfigDeprecation = (settings, fromPath, log) => { +const logFilterDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { if (has(settings, 'logging.filter')) { - log('"logging.filter" has been deprecated and will be removed ' + 'in 8.0. '); + addDeprecation({ + documentationUrl: + 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.mdx#loggingfilter', + message: '"logging.filter" has been deprecated and will be removed in 8.0.', + }); } return settings; }; diff --git a/src/core/server/config/index.ts b/src/core/server/config/index.ts index bf2ce16e869b7..b1086d4470335 100644 --- a/src/core/server/config/index.ts +++ b/src/core/server/config/index.ts @@ -24,7 +24,7 @@ export type { ConfigPath, CliArgs, ConfigDeprecation, - ConfigDeprecationLogger, + AddConfigDeprecation, ConfigDeprecationProvider, ConfigDeprecationFactory, EnvironmentMode, diff --git a/src/core/server/deprecations/deprecations_factory.test.ts b/src/core/server/deprecations/deprecations_factory.test.ts new file mode 100644 index 0000000000000..469451b0020c0 --- /dev/null +++ b/src/core/server/deprecations/deprecations_factory.test.ts @@ -0,0 +1,248 @@ +/* + * 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 { GetDeprecationsContext } from './types'; +import { DeprecationsFactory } from './deprecations_factory'; +import { loggerMock } from '../logging/logger.mock'; + +describe('DeprecationsFactory', () => { + const logger = loggerMock.create(); + beforeEach(() => { + loggerMock.clear(logger); + }); + + describe('getRegistry', () => { + const domainId = 'test-plugin'; + + it('creates a registry for a domainId', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const registry = deprecationsFactory.getRegistry(domainId); + + expect(registry).toHaveProperty('registerDeprecations'); + expect(registry).toHaveProperty('getDeprecations'); + }); + + it('creates one registry for a domainId', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const registry = deprecationsFactory.getRegistry(domainId); + const sameRegistry = deprecationsFactory.getRegistry(domainId); + + expect(registry).toStrictEqual(sameRegistry); + }); + + it('returns a registered registry', () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const mockRegistry = 'mock-reg'; + const mockRegistries = { + set: jest.fn(), + get: jest.fn().mockReturnValue(mockRegistry), + }; + + // @ts-expect-error + deprecationsFactory.registries = mockRegistries; + const result = deprecationsFactory.getRegistry(domainId); + + expect(mockRegistries.get).toBeCalledTimes(1); + expect(mockRegistries.get).toBeCalledWith(domainId); + expect(mockRegistries.set).toBeCalledTimes(0); + expect(result).toStrictEqual(mockRegistry); + }); + }); + + describe('getAllDeprecations', () => { + const mockDependencies = ({ + esClient: jest.fn(), + savedObjectsClient: jest.fn(), + } as unknown) as GetDeprecationsContext; + + it('returns a flattened array of deprecations', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const mockPluginDeprecationsInfo = [ + { + message: 'mockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['mockPlugin step 1', 'mockPlugin step 2'], + }, + }, + { + message: 'hello there!', + level: 'warning', + correctiveActions: { + manualSteps: ['mockPlugin step a', 'mockPlugin step b'], + }, + }, + ]; + const anotherMockPluginDeprecationsInfo = [ + { + message: 'anotherMockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['anotherMockPlugin step 1', 'anotherMockPlugin step 2'], + }, + }, + ]; + + const mockPluginRegistry = deprecationsFactory.getRegistry('mockPlugin'); + const anotherMockPluginRegistry = deprecationsFactory.getRegistry('anotherMockPlugin'); + mockPluginRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(mockPluginDeprecationsInfo), + }); + anotherMockPluginRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(anotherMockPluginDeprecationsInfo), + }); + + const derpecations = await deprecationsFactory.getAllDeprecations(mockDependencies); + expect(derpecations).toStrictEqual( + [ + mockPluginDeprecationsInfo.map((info) => ({ ...info, domainId: 'mockPlugin' })), + anotherMockPluginDeprecationsInfo.map((info) => ({ + ...info, + domainId: 'anotherMockPlugin', + })), + ].flat() + ); + }); + + it(`returns a failure message for failed getDeprecations functions`, async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const domainId = 'mockPlugin'; + const mockError = new Error(); + + const deprecationsRegistry = deprecationsFactory.getRegistry(domainId); + deprecationsRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockRejectedValue(mockError), + }); + const derpecations = await deprecationsFactory.getAllDeprecations(mockDependencies); + expect(logger.warn).toBeCalledTimes(1); + expect(logger.warn).toBeCalledWith( + `Failed to get deprecations info for plugin "${domainId}".`, + mockError + ); + expect(derpecations).toStrictEqual([ + { + domainId, + message: `Failed to get deprecations info for plugin "${domainId}".`, + level: 'fetch_error', + correctiveActions: { + manualSteps: ['Check Kibana server logs for error message.'], + }, + }, + ]); + }); + + it(`returns successful results even when some getDeprecations fail`, async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const mockPluginRegistry = deprecationsFactory.getRegistry('mockPlugin'); + const anotherMockPluginRegistry = deprecationsFactory.getRegistry('anotherMockPlugin'); + const mockError = new Error(); + const mockPluginDeprecationsInfo = [ + { + message: 'mockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['mockPlugin step 1', 'mockPlugin step 2'], + }, + }, + ]; + mockPluginRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(mockPluginDeprecationsInfo), + }); + anotherMockPluginRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockRejectedValue(mockError), + }); + const derpecations = await deprecationsFactory.getAllDeprecations(mockDependencies); + + expect(logger.warn).toBeCalledTimes(1); + expect(logger.warn).toBeCalledWith( + `Failed to get deprecations info for plugin "anotherMockPlugin".`, + mockError + ); + expect(derpecations).toStrictEqual([ + ...mockPluginDeprecationsInfo.map((info) => ({ ...info, domainId: 'mockPlugin' })), + { + domainId: 'anotherMockPlugin', + message: `Failed to get deprecations info for plugin "anotherMockPlugin".`, + level: 'fetch_error', + correctiveActions: { + manualSteps: ['Check Kibana server logs for error message.'], + }, + }, + ]); + }); + }); + + describe('getDeprecations', () => { + const mockDependencies = ({ + esClient: jest.fn(), + savedObjectsClient: jest.fn(), + } as unknown) as GetDeprecationsContext; + + it('returns a flattened array of DeprecationInfo', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const deprecationsRegistry = deprecationsFactory.getRegistry('mockPlugin'); + const deprecationsBody = [ + { + message: 'mockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['mockPlugin step 1', 'mockPlugin step 2'], + }, + }, + [ + { + message: 'hello there!', + level: 'warning', + correctiveActions: { + manualSteps: ['mockPlugin step a', 'mockPlugin step b'], + }, + }, + ], + ]; + + deprecationsRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(deprecationsBody), + }); + + const derpecations = await deprecationsFactory.getDeprecations( + 'mockPlugin', + mockDependencies + ); + expect(derpecations).toStrictEqual( + deprecationsBody.flat().map((body) => ({ ...body, domainId: 'mockPlugin' })) + ); + }); + + it('removes empty entries from the returned array', async () => { + const deprecationsFactory = new DeprecationsFactory({ logger }); + const deprecationsRegistry = deprecationsFactory.getRegistry('mockPlugin'); + const deprecationsBody = [ + { + message: 'mockPlugin message', + level: 'critical', + correctiveActions: { + manualSteps: ['mockPlugin step 1', 'mockPlugin step 2'], + }, + }, + [undefined], + undefined, + ]; + + deprecationsRegistry.registerDeprecations({ + getDeprecations: jest.fn().mockResolvedValue(deprecationsBody), + }); + + const derpecations = await deprecationsFactory.getDeprecations( + 'mockPlugin', + mockDependencies + ); + expect(derpecations).toHaveLength(1); + expect(derpecations).toStrictEqual([{ ...deprecationsBody[0], domainId: 'mockPlugin' }]); + }); + }); +}); diff --git a/src/core/server/deprecations/deprecations_factory.ts b/src/core/server/deprecations/deprecations_factory.ts new file mode 100644 index 0000000000000..3699c088e20f1 --- /dev/null +++ b/src/core/server/deprecations/deprecations_factory.ts @@ -0,0 +1,108 @@ +/* + * 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 { DeprecationsRegistry } from './deprecations_registry'; +import type { Logger } from '../logging'; +import type { + DomainDeprecationDetails, + DeprecationsDetails, + GetDeprecationsContext, +} from './types'; + +export interface DeprecationsFactoryDeps { + logger: Logger; +} + +export class DeprecationsFactory { + private readonly registries: Map = new Map(); + private readonly logger: Logger; + constructor({ logger }: DeprecationsFactoryDeps) { + this.logger = logger; + } + + public getRegistry = (domainId: string): DeprecationsRegistry => { + const existing = this.registries.get(domainId); + if (existing) { + return existing; + } + const registry = new DeprecationsRegistry(); + this.registries.set(domainId, registry); + return registry; + }; + + public getDeprecations = async ( + domainId: string, + dependencies: GetDeprecationsContext + ): Promise => { + const infoBody = await this.getDeprecationsBody(domainId, dependencies); + return this.createDeprecationInfo(domainId, infoBody).flat(); + }; + + public getAllDeprecations = async ( + dependencies: GetDeprecationsContext + ): Promise => { + const domainIds = [...this.registries.keys()]; + + const deprecationsInfo = await Promise.all( + domainIds.map(async (domainId) => { + const infoBody = await this.getDeprecationsBody(domainId, dependencies); + return this.createDeprecationInfo(domainId, infoBody); + }) + ); + + return deprecationsInfo.flat(); + }; + + private createDeprecationInfo = ( + domainId: string, + deprecationInfoBody: DeprecationsDetails[] + ): DomainDeprecationDetails[] => { + return deprecationInfoBody + .flat() + .filter(Boolean) + .map((pluginDeprecation) => ({ + ...pluginDeprecation, + domainId, + })); + }; + + private getDeprecationsBody = async ( + domainId: string, + dependencies: GetDeprecationsContext + ): Promise => { + const deprecationsRegistry = this.registries.get(domainId); + if (!deprecationsRegistry) { + return []; + } + try { + const settledResults = await deprecationsRegistry.getDeprecations(dependencies); + return settledResults.flatMap((settledResult) => { + if (settledResult.status === 'rejected') { + this.logger.warn( + `Failed to get deprecations info for plugin "${domainId}".`, + settledResult.reason + ); + return [ + { + message: `Failed to get deprecations info for plugin "${domainId}".`, + level: 'fetch_error', + correctiveActions: { + manualSteps: ['Check Kibana server logs for error message.'], + }, + }, + ]; + } + + return settledResult.value; + }); + } catch (err) { + this.logger.warn(`Failed to get deprecations info for plugin "${domainId}".`, err); + return []; + } + }; +} diff --git a/src/core/server/deprecations/deprecations_registry.test.ts b/src/core/server/deprecations/deprecations_registry.test.ts new file mode 100644 index 0000000000000..507677a531861 --- /dev/null +++ b/src/core/server/deprecations/deprecations_registry.test.ts @@ -0,0 +1,78 @@ +/* + * 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. + */ + +/* eslint-disable dot-notation */ +import { RegisterDeprecationsConfig, GetDeprecationsContext } from './types'; +import { DeprecationsRegistry } from './deprecations_registry'; + +describe('DeprecationsRegistry', () => { + describe('registerDeprecations', () => { + it('throws if getDeprecations is not a function', async () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const deprecationsConfig = ({ + getDeprecations: null, + } as unknown) as RegisterDeprecationsConfig; + expect(() => deprecationsRegistry.registerDeprecations(deprecationsConfig)).toThrowError( + /getDeprecations must be a function/ + ); + }); + + it('registers deprecation context', () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const getDeprecations = jest.fn(); + const deprecationsConfig = { getDeprecations }; + deprecationsRegistry.registerDeprecations(deprecationsConfig); + expect(deprecationsRegistry['deprecationContexts']).toStrictEqual([deprecationsConfig]); + }); + + it('allows registering multiple contexts', async () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const deprecationsConfigA = { getDeprecations: jest.fn() }; + const deprecationsConfigB = { getDeprecations: jest.fn() }; + deprecationsRegistry.registerDeprecations(deprecationsConfigA); + deprecationsRegistry.registerDeprecations(deprecationsConfigB); + expect(deprecationsRegistry['deprecationContexts']).toStrictEqual([ + deprecationsConfigA, + deprecationsConfigB, + ]); + }); + }); + + describe('getDeprecations', () => { + it('returns all settled deprecations', async () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const mockContext = ({} as unknown) as GetDeprecationsContext; + const mockError = new Error(); + const deprecationsConfigA = { getDeprecations: jest.fn().mockResolvedValue('hi') }; + const deprecationsConfigB = { getDeprecations: jest.fn().mockRejectedValue(mockError) }; + deprecationsRegistry.registerDeprecations(deprecationsConfigA); + deprecationsRegistry.registerDeprecations(deprecationsConfigB); + const deprecations = await deprecationsRegistry.getDeprecations(mockContext); + expect(deprecations).toStrictEqual([ + { + status: 'fulfilled', + value: 'hi', + }, + { + status: 'rejected', + reason: mockError, + }, + ]); + }); + + it('passes dependencies to registered getDeprecations function', async () => { + const deprecationsRegistry = new DeprecationsRegistry(); + const mockContext = ({} as unknown) as GetDeprecationsContext; + const deprecationsConfig = { getDeprecations: jest.fn().mockResolvedValue('hi') }; + deprecationsRegistry.registerDeprecations(deprecationsConfig); + const deprecations = await deprecationsRegistry.getDeprecations(mockContext); + expect(deprecations).toHaveLength(1); + expect(deprecationsConfig.getDeprecations).toBeCalledWith(mockContext); + }); + }); +}); diff --git a/src/core/server/deprecations/deprecations_registry.ts b/src/core/server/deprecations/deprecations_registry.ts new file mode 100644 index 0000000000000..f92d807514b82 --- /dev/null +++ b/src/core/server/deprecations/deprecations_registry.ts @@ -0,0 +1,31 @@ +/* + * 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 { DeprecationsDetails, RegisterDeprecationsConfig, GetDeprecationsContext } from './types'; + +export class DeprecationsRegistry { + private readonly deprecationContexts: RegisterDeprecationsConfig[] = []; + + public registerDeprecations = (deprecationContext: RegisterDeprecationsConfig) => { + if (typeof deprecationContext.getDeprecations !== 'function') { + throw new Error(`getDeprecations must be a function in registerDeprecations(context)`); + } + + this.deprecationContexts.push(deprecationContext); + }; + + public getDeprecations = async ( + dependencies: GetDeprecationsContext + ): Promise>> => { + return await Promise.allSettled( + this.deprecationContexts.map( + async (deprecationContext) => await deprecationContext.getDeprecations(dependencies) + ) + ); + }; +} diff --git a/src/core/server/deprecations/deprecations_service.mock.ts b/src/core/server/deprecations/deprecations_service.mock.ts new file mode 100644 index 0000000000000..c0febf90a489a --- /dev/null +++ b/src/core/server/deprecations/deprecations_service.mock.ts @@ -0,0 +1,49 @@ +/* + * 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 { PublicMethodsOf } from '@kbn/utility-types'; +import { + DeprecationsService, + InternalDeprecationsServiceSetup, + DeprecationsServiceSetup, +} from './deprecations_service'; +type DeprecationsServiceContract = PublicMethodsOf; + +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + registerDeprecations: jest.fn(), + }; + + return setupContract; +}; + +const createInternalSetupContractMock = () => { + const internalSetupContract: jest.Mocked = { + getRegistry: jest.fn(), + }; + + internalSetupContract.getRegistry.mockReturnValue(createSetupContractMock()); + return internalSetupContract; +}; + +const createDeprecationsServiceMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; + + mocked.setup.mockReturnValue(createInternalSetupContractMock()); + return mocked; +}; + +export const deprecationsServiceMock = { + create: createDeprecationsServiceMock, + createInternalSetupContract: createInternalSetupContractMock, + createSetupContract: createSetupContractMock, +}; diff --git a/src/core/server/deprecations/deprecations_service.ts b/src/core/server/deprecations/deprecations_service.ts new file mode 100644 index 0000000000000..8eca1ba5790c5 --- /dev/null +++ b/src/core/server/deprecations/deprecations_service.ts @@ -0,0 +1,168 @@ +/* + * 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 { DeprecationsFactory } from './deprecations_factory'; +import { RegisterDeprecationsConfig } from './types'; +import { registerRoutes } from './routes'; + +import { CoreContext } from '../core_context'; +import { CoreUsageDataSetup } from '../core_usage_data'; +import { InternalElasticsearchServiceSetup } from '../elasticsearch'; +import { CoreService } from '../../types'; +import { InternalHttpServiceSetup } from '../http'; +import { Logger } from '../logging'; + +/** + * The deprecations service provides a way for the Kibana platform to communicate deprecated + * features and configs with its users. These deprecations are only communicated + * if the deployment is using these features. Allowing for a user tailored experience + * for upgrading the stack version. + * + * The Deprecation service is consumed by the upgrade assistant to assist with the upgrade + * experience. + * + * If a deprecated feature can be resolved without manual user intervention. + * Using correctiveActions.api allows the Upgrade Assistant to use this api to correct the + * deprecation upon a user trigger. + * + * @example + * ```ts + * import { DeprecationsDetails, GetDeprecationsContext, CoreSetup } from 'src/core/server'; + * + * async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise { + * const deprecations: DeprecationsDetails[] = []; + * const count = await getTimelionSheetsCount(savedObjectsClient); + * + * if (count > 0) { + * // Example of a manual correctiveAction + * deprecations.push({ + * message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.`, + * documentationUrl: + * 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', + * level: 'warning', + * correctiveActions: { + * manualSteps: [ + * 'Navigate to the Kibana Dashboard and click "Create dashboard".', + * 'Select Timelion from the "New Visualization" window.', + * 'Open a new tab, open the Timelion app, select the chart you want to copy, then copy the chart expression.', + * 'Go to Timelion, paste the chart expression in the Timelion expression field, then click Update.', + * 'In the toolbar, click Save.', + * 'On the Save visualization window, enter the visualization Title, then click Save and return.', + * ], + * }, + * }); + * } + * + * // Example of an api correctiveAction + * deprecations.push({ + * "message": "User 'test_dashboard_user' is using a deprecated role: 'kibana_user'", + * "documentationUrl": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html", + * "level": "critical", + * "correctiveActions": { + * "api": { + * "path": "/internal/security/users/test_dashboard_user", + * "method": "POST", + * "body": { + * "username": "test_dashboard_user", + * "roles": [ + * "machine_learning_user", + * "enrich_user", + * "kibana_admin" + * ], + * "full_name": "Alison Goryachev", + * "email": "alisongoryachev@gmail.com", + * "metadata": {}, + * "enabled": true + * } + * }, + * "manualSteps": [ + * "Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.", + * "Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role." + * ] + * }, + * }); + * + * return deprecations; + * } + * + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.deprecations.registerDeprecations({ getDeprecations }); + * } + * } + * ``` + * + * @public + */ +export interface DeprecationsServiceSetup { + registerDeprecations: (deprecationContext: RegisterDeprecationsConfig) => void; +} + +/** @internal */ +export interface InternalDeprecationsServiceSetup { + getRegistry: (domainId: string) => DeprecationsServiceSetup; +} + +/** @internal */ +export interface DeprecationsSetupDeps { + http: InternalHttpServiceSetup; + elasticsearch: InternalElasticsearchServiceSetup; + coreUsageData: CoreUsageDataSetup; +} + +/** @internal */ +export class DeprecationsService implements CoreService { + private readonly logger: Logger; + + constructor(private readonly coreContext: CoreContext) { + this.logger = coreContext.logger.get('deprecations-service'); + } + + public setup({ http }: DeprecationsSetupDeps): InternalDeprecationsServiceSetup { + this.logger.debug('Setting up Deprecations service'); + const deprecationsFactory = new DeprecationsFactory({ + logger: this.logger, + }); + + registerRoutes({ http, deprecationsFactory }); + this.registerConfigDeprecationsInfo(deprecationsFactory); + + return { + getRegistry: (domainId: string): DeprecationsServiceSetup => { + const registry = deprecationsFactory.getRegistry(domainId); + return { + registerDeprecations: registry.registerDeprecations, + }; + }, + }; + } + + public start() {} + public stop() {} + + private registerConfigDeprecationsInfo(deprecationsFactory: DeprecationsFactory) { + const handledDeprecatedConfigs = this.coreContext.configService.getHandledDeprecatedConfigs(); + + for (const [domainId, deprecationsContexts] of handledDeprecatedConfigs) { + const deprecationsRegistry = deprecationsFactory.getRegistry(domainId); + deprecationsRegistry.registerDeprecations({ + getDeprecations: () => { + return deprecationsContexts.map(({ message, correctiveActions, documentationUrl }) => { + return { + level: 'critical', + message, + correctiveActions: correctiveActions ?? {}, + documentationUrl, + }; + }); + }, + }); + } + } +} diff --git a/src/core/server/deprecations/index.ts b/src/core/server/deprecations/index.ts new file mode 100644 index 0000000000000..c7d1a13800694 --- /dev/null +++ b/src/core/server/deprecations/index.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export type { + DeprecationsDetails, + GetDeprecationsContext, + RegisterDeprecationsConfig, + DeprecationsGetResponse, +} from './types'; + +export type { + DeprecationsServiceSetup, + InternalDeprecationsServiceSetup, +} from './deprecations_service'; + +export { DeprecationsService } from './deprecations_service'; diff --git a/src/core/server/deprecations/routes/get.ts b/src/core/server/deprecations/routes/get.ts new file mode 100644 index 0000000000000..fed3fcfbd1809 --- /dev/null +++ b/src/core/server/deprecations/routes/get.ts @@ -0,0 +1,35 @@ +/* + * 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 { IRouter } from '../../http'; +import { GetDeprecationsContext, DeprecationsGetResponse } from '../types'; +import { DeprecationsFactory } from '../deprecations_factory'; + +interface RouteDependencies { + deprecationsFactory: DeprecationsFactory; +} + +export const registerGetRoute = (router: IRouter, { deprecationsFactory }: RouteDependencies) => { + router.get( + { + path: '/', + validate: false, + }, + async (context, req, res) => { + const dependencies: GetDeprecationsContext = { + esClient: context.core.elasticsearch.client, + savedObjectsClient: context.core.savedObjects.client, + }; + + const body: DeprecationsGetResponse = { + deprecations: await deprecationsFactory.getAllDeprecations(dependencies), + }; + + return res.ok({ body }); + } + ); +}; diff --git a/src/core/server/deprecations/routes/index.ts b/src/core/server/deprecations/routes/index.ts new file mode 100644 index 0000000000000..db58bec29f7b8 --- /dev/null +++ b/src/core/server/deprecations/routes/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { InternalHttpServiceSetup } from '../../http'; +import { registerGetRoute } from './get'; +import { DeprecationsFactory } from '../deprecations_factory'; + +export function registerRoutes({ + http, + deprecationsFactory, +}: { + http: InternalHttpServiceSetup; + deprecationsFactory: DeprecationsFactory; +}) { + const router = http.createRouter('/api/deprecations'); + registerGetRoute(router, { deprecationsFactory }); +} diff --git a/src/core/server/deprecations/types.ts b/src/core/server/deprecations/types.ts new file mode 100644 index 0000000000000..31734b51b46bd --- /dev/null +++ b/src/core/server/deprecations/types.ts @@ -0,0 +1,67 @@ +/* + * 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 { SavedObjectsClientContract } from '../saved_objects/types'; +import type { IScopedClusterClient } from '../elasticsearch'; + +type MaybePromise = T | Promise; + +export interface DomainDeprecationDetails extends DeprecationsDetails { + domainId: string; +} + +export interface DeprecationsDetails { + /* The message to be displayed for the deprecation. */ + message: string; + /** + * levels: + * - warning: will not break deployment upon upgrade + * - critical: needs to be addressed before upgrade. + * - fetch_error: Deprecations service failed to grab the deprecation details for the domain. + */ + level: 'warning' | 'critical' | 'fetch_error'; + /* (optional) link to the documentation for more details on the deprecation. */ + documentationUrl?: string; + /* corrective action needed to fix this deprecation. */ + correctiveActions: { + /** + * (optional) The api to be called to automatically fix the deprecation + * Each domain should implement a POST/PUT route for their plugin to + * handle their deprecations. + */ + api?: { + /* Kibana route path. Passing a query string is allowed */ + path: string; + /* Kibana route method: 'POST' or 'PUT'. */ + method: 'POST' | 'PUT'; + /* Additional details to be passed to the route. */ + body?: { + [key: string]: any; + }; + }; + /** + * (optional) If this deprecation cannot be automtically fixed + * via an API corrective action. Specify a list of manual steps + * users need to follow to fix the deprecation before upgrade. + */ + manualSteps?: string[]; + }; +} + +export interface RegisterDeprecationsConfig { + getDeprecations: (context: GetDeprecationsContext) => MaybePromise; +} + +export interface GetDeprecationsContext { + esClient: IScopedClusterClient; + savedObjectsClient: SavedObjectsClientContract; +} + +export interface DeprecationsGetResponse { + deprecations: DomainDeprecationDetails[]; +} diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index 4b6cf220ccd52..23b804b535405 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -28,7 +28,7 @@ const applyElasticsearchDeprecations = (settings: Record = {}) => { deprecation, path: CONFIG_PATH, })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index d3432344f5a73..e731af4817955 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -144,32 +144,32 @@ export const configSchema = schema.object({ }); const deprecations: ConfigDeprecationProvider = () => [ - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const es = settings[fromPath]; if (!es) { return settings; } if (es.username === 'elastic') { - log( - `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana_system" user instead.` - ); + addDeprecation({ + message: `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana_system" user instead.`, + }); } else if (es.username === 'kibana') { - log( - `Setting [${fromPath}.username] to "kibana" is deprecated. You should use the "kibana_system" user instead.` - ); + addDeprecation({ + message: `Setting [${fromPath}.username] to "kibana" is deprecated. You should use the "kibana_system" user instead.`, + }); } if (es.ssl?.key !== undefined && es.ssl?.certificate === undefined) { - log( - `Setting [${fromPath}.ssl.key] without [${fromPath}.ssl.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` - ); + addDeprecation({ + message: `Setting [${fromPath}.ssl.key] without [${fromPath}.ssl.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`, + }); } else if (es.ssl?.certificate !== undefined && es.ssl?.key === undefined) { - log( - `Setting [${fromPath}.ssl.certificate] without [${fromPath}.ssl.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` - ); + addDeprecation({ + message: `Setting [${fromPath}.ssl.certificate] without [${fromPath}.ssl.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`, + }); } else if (es.logQueries === true) { - log( - `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers" or use "logging.verbose: true".` - ); + addDeprecation({ + message: `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.queries" context in "logging.loggers" or use "logging.verbose: true".`, + }); } return settings; }, diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 788c179501a80..963b69eac4f7f 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -57,7 +57,7 @@ import { StatusServiceSetup } from './status'; import { AppenderConfigType, appendersSchema, LoggingServiceSetup } from './logging'; import { CoreUsageDataStart } from './core_usage_data'; import { I18nServiceSetup } from './i18n'; - +import { DeprecationsServiceSetup } from './deprecations'; // Because of #79265 we need to explicity import, then export these types for // scripts/telemetry_check.js to work as expected import { @@ -88,8 +88,8 @@ export type { ConfigService, ConfigDeprecation, ConfigDeprecationProvider, - ConfigDeprecationLogger, ConfigDeprecationFactory, + AddConfigDeprecation, EnvironmentMode, PackageInfo, } from './config'; @@ -381,6 +381,12 @@ export type { } from './metrics'; export type { I18nServiceSetup } from './i18n'; +export type { + DeprecationsDetails, + RegisterDeprecationsConfig, + GetDeprecationsContext, + DeprecationsServiceSetup, +} from './deprecations'; export type { AppCategory } from '../types'; export { DEFAULT_APP_CATEGORIES } from '../utils'; @@ -481,6 +487,8 @@ export interface CoreSetup; } diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 28194a7d0dc3a..34193f8d0c35e 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -29,6 +29,7 @@ import { InternalStatusServiceSetup } from './status'; import { InternalLoggingServiceSetup } from './logging'; import { CoreUsageDataStart } from './core_usage_data'; import { I18nServiceSetup } from './i18n'; +import { InternalDeprecationsServiceSetup } from './deprecations'; /** @internal */ export interface InternalCoreSetup { @@ -45,6 +46,7 @@ export interface InternalCoreSetup { httpResources: InternalHttpResourcesSetup; logging: InternalLoggingServiceSetup; metrics: InternalMetricsServiceSetup; + deprecations: InternalDeprecationsServiceSetup; } /** diff --git a/src/core/server/kibana_config.test.ts b/src/core/server/kibana_config.test.ts index 1c2b268156531..1acdff9dd78e6 100644 --- a/src/core/server/kibana_config.test.ts +++ b/src/core/server/kibana_config.test.ts @@ -22,7 +22,7 @@ const applyKibanaDeprecations = (settings: Record = {}) => { deprecation, path: CONFIG_PATH, })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/src/core/server/kibana_config.ts b/src/core/server/kibana_config.ts index d0ff18b381179..97783a7657db5 100644 --- a/src/core/server/kibana_config.ts +++ b/src/core/server/kibana_config.ts @@ -12,12 +12,13 @@ import { ConfigDeprecationProvider } from '@kbn/config'; export type KibanaConfigType = TypeOf; const deprecations: ConfigDeprecationProvider = () => [ - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const kibana = settings[fromPath]; if (kibana?.index) { - log( - `"kibana.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details` - ); + addDeprecation({ + message: `"kibana.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details`, + documentationUrl: 'https://ela.st/kbn-remove-legacy-multitenancy', + }); } return settings; }, diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index db36bd73602c4..d0a02b9859960 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -32,6 +32,7 @@ import { statusServiceMock } from '../status/status_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { metricsServiceMock } from '../metrics/metrics_service.mock'; import { i18nServiceMock } from '../i18n/i18n_service.mock'; +import { deprecationsServiceMock } from '../deprecations/deprecations_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -80,6 +81,7 @@ beforeEach(() => { status: statusServiceMock.createInternalSetupContract(), logging: loggingServiceMock.createInternalSetupContract(), metrics: metricsServiceMock.createInternalSetupContract(), + deprecations: deprecationsServiceMock.createInternalSetupContract(), }, plugins: { 'plugin-id': 'plugin-value' }, uiPlugins: { diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index f7abe942d0009..43b348a5ff4a2 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -257,6 +257,11 @@ export class LegacyService implements CoreService { uiSettings: { register: setupDeps.core.uiSettings.register, }, + deprecations: { + registerDeprecations: () => { + throw new Error('core.setup.deprecations.registerDeprecations is unsupported in legacy'); + }, + }, getStartServices: () => Promise.resolve([coreStart, startDeps.plugins, {}]), }; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 19056ae1b9bc7..cd0ce7005cc41 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -29,6 +29,7 @@ import { environmentServiceMock } from './environment/environment_service.mock'; import { statusServiceMock } from './status/status_service.mock'; import { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock'; import { i18nServiceMock } from './i18n/i18n_service.mock'; +import { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; export { configServiceMock } from './config/mocks'; export { httpServerMock } from './http/http_server.mocks'; @@ -49,6 +50,7 @@ export { contextServiceMock } from './context/context_service.mock'; export { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; export { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock'; export { i18nServiceMock } from './i18n/i18n_service.mock'; +export { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; export function pluginInitializerContextConfigMock(config: T) { const globalConfig: SharedGlobalConfig = { @@ -137,6 +139,7 @@ function createCoreSetupMock({ uiSettings: uiSettingsMock, logging: loggingServiceMock.createSetupContract(), metrics: metricsServiceMock.createSetupContract(), + deprecations: deprecationsServiceMock.createSetupContract(), getStartServices: jest .fn, object, any]>, []>() .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), @@ -174,6 +177,7 @@ function createInternalCoreSetupMock() { uiSettings: uiSettingsServiceMock.createSetupContract(), logging: loggingServiceMock.createInternalSetupContract(), metrics: metricsServiceMock.createInternalSetupContract(), + deprecations: deprecationsServiceMock.createInternalSetupContract(), }; return setupDeps; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 74451f38b893e..c466eb2b9ee09 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -165,6 +165,7 @@ export function createPluginSetupContext( register: deps.uiSettings.register, }, getStartServices: () => plugin.startDependencies, + deprecations: deps.deprecations.getRegistry(plugin.name), }; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 551471d3d3ba8..de96c5ccfb81e 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -4,6 +4,7 @@ ```ts +import { AddConfigDeprecation } from '@kbn/config'; import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from '@hapi/boom'; import { BulkIndexDocumentsParams } from 'elasticsearch'; @@ -35,7 +36,6 @@ import { ClusterStateParams } from 'elasticsearch'; import { ClusterStatsParams } from 'elasticsearch'; import { ConfigDeprecation } from '@kbn/config'; import { ConfigDeprecationFactory } from '@kbn/config'; -import { ConfigDeprecationLogger } from '@kbn/config'; import { ConfigDeprecationProvider } from '@kbn/config'; import { ConfigOptions } from 'elasticsearch'; import { ConfigPath } from '@kbn/config'; @@ -169,6 +169,8 @@ import { UpdateDocumentByQueryParams } from 'elasticsearch'; import { UpdateDocumentParams } from 'elasticsearch'; import { URL } from 'url'; +export { AddConfigDeprecation } + // @public export interface AppCategory { ariaLabel?: string; @@ -374,8 +376,6 @@ export { ConfigDeprecation } export { ConfigDeprecationFactory } -export { ConfigDeprecationLogger } - export { ConfigDeprecationProvider } export { ConfigPath } @@ -491,6 +491,8 @@ export interface CoreSetup; @@ -830,12 +832,40 @@ export interface DeprecationInfo { url: string; } +// Warning: (ae-missing-release-tag) "DeprecationsDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DeprecationsDetails { + // (undocumented) + correctiveActions: { + api?: { + path: string; + method: 'POST' | 'PUT'; + body?: { + [key: string]: any; + }; + }; + manualSteps?: string[]; + }; + // (undocumented) + documentationUrl?: string; + level: 'warning' | 'critical' | 'fetch_error'; + // (undocumented) + message: string; +} + // @public export interface DeprecationSettings { docLinksKey: string; message: string; } +// @public +export interface DeprecationsServiceSetup { + // (undocumented) + registerDeprecations: (deprecationContext: RegisterDeprecationsConfig) => void; +} + // @public export type DestructiveRouteMethod = 'post' | 'put' | 'delete' | 'patch'; @@ -939,6 +969,16 @@ export type GetAuthState = (request: KibanaRequest | LegacyRequest) state: T; }; +// Warning: (ae-missing-release-tag) "GetDeprecationsContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface GetDeprecationsContext { + // (undocumented) + esClient: IScopedClusterClient; + // (undocumented) + savedObjectsClient: SavedObjectsClientContract; +} + // @public (undocumented) export interface GetResponse { // (undocumented) @@ -1912,6 +1952,16 @@ export type RedirectResponseOptions = HttpResponseOptions & { }; }; +// Warning: (ae-missing-release-tag) "RegisterDeprecationsConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface RegisterDeprecationsConfig { + // Warning: (ae-forgotten-export) The symbol "MaybePromise" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getDeprecations: (context: GetDeprecationsContext) => MaybePromise; +} + // @public export type RequestHandler

= (context: Context, request: KibanaRequest, response: ResponseFactory) => IKibanaResponse | Promise>; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 8905bcd28fe17..b575b2779082c 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -41,6 +41,7 @@ import { ContextService } from './context'; import { RequestHandlerContext } from '.'; import { InternalCoreSetup, InternalCoreStart, ServiceConfigDescriptor } from './internal_types'; import { CoreUsageDataService } from './core_usage_data'; +import { DeprecationsService } from './deprecations'; import { CoreRouteHandlerContext } from './core_route_handler_context'; import { config as externalUrlConfig } from './external_url'; @@ -67,6 +68,7 @@ export class Server { private readonly coreApp: CoreApp; private readonly coreUsageData: CoreUsageDataService; private readonly i18n: I18nService; + private readonly deprecations: DeprecationsService; private readonly savedObjectsStartPromise: Promise; private resolveSavedObjectsStartPromise?: (value: SavedObjectsServiceStart) => void; @@ -102,6 +104,7 @@ export class Server { this.logging = new LoggingService(core); this.coreUsageData = new CoreUsageDataService(core); this.i18n = new I18nService(core); + this.deprecations = new DeprecationsService(core); this.savedObjectsStartPromise = new Promise((resolve) => { this.resolveSavedObjectsStartPromise = resolve; @@ -192,6 +195,12 @@ export class Server { loggingSystem: this.loggingSystem, }); + const deprecationsSetup = this.deprecations.setup({ + http: httpSetup, + elasticsearch: elasticsearchServiceSetup, + coreUsageData: coreUsageDataSetup, + }); + const coreSetup: InternalCoreSetup = { capabilities: capabilitiesSetup, context: contextServiceSetup, @@ -206,6 +215,7 @@ export class Server { httpResources: httpResourcesSetup, logging: loggingSetup, metrics: metricsSetup, + deprecations: deprecationsSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); @@ -285,6 +295,7 @@ export class Server { await this.metrics.stop(); await this.status.stop(); await this.logging.stop(); + this.deprecations.stop(); } private registerCoreContext(coreSetup: InternalCoreSetup) { diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 6bd805d55af1d..ab1d6c6d95d0a 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -37,6 +37,7 @@ export type { SavedObjectsClientContract, SavedObjectsNamespaceType, } from './saved_objects/types'; +export type { DomainDeprecationDetails, DeprecationsGetResponse } from './deprecations/types'; export * from './ui_settings/types'; export * from './legacy/types'; export type { EnvironmentMode, PackageInfo } from '@kbn/config'; diff --git a/src/plugins/kibana_legacy/server/index.ts b/src/plugins/kibana_legacy/server/index.ts index 15511e1e9f7f2..1402416d69c96 100644 --- a/src/plugins/kibana_legacy/server/index.ts +++ b/src/plugins/kibana_legacy/server/index.ts @@ -6,12 +6,7 @@ * Side Public License, v 1. */ -import { - ConfigDeprecationLogger, - CoreSetup, - CoreStart, - PluginConfigDescriptor, -} from 'kibana/server'; +import { AddConfigDeprecation, CoreSetup, CoreStart, PluginConfigDescriptor } from 'kibana/server'; import { get } from 'lodash'; import { configSchema, ConfigSchema } from '../config'; @@ -23,17 +18,28 @@ export const config: PluginConfigDescriptor = { schema: configSchema, deprecations: ({ renameFromRoot }) => [ // TODO: Remove deprecation once defaultAppId is deleted - renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', true), - (completeConfig: Record, rootPath: string, log: ConfigDeprecationLogger) => { + renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', { silent: true }), + ( + completeConfig: Record, + rootPath: string, + addDeprecation: AddConfigDeprecation + ) => { if ( get(completeConfig, 'kibana.defaultAppId') === undefined && get(completeConfig, 'kibana_legacy.defaultAppId') === undefined ) { return completeConfig; } - log( - `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the \`defaultRoute\` advanced setting instead` - ); + addDeprecation({ + message: `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the \`defaultRoute\` advanced setting instead`, + correctiveActions: { + manualSteps: [ + 'Go to Stack Management > Advanced Settings', + 'Update the "defaultRoute" setting under the General section', + 'Remove "kibana.defaultAppId" from the kibana.yml config file', + ], + }, + }); return completeConfig; }, ], diff --git a/src/plugins/timelion/server/deprecations.ts b/src/plugins/timelion/server/deprecations.ts new file mode 100644 index 0000000000000..3d4e687f154cf --- /dev/null +++ b/src/plugins/timelion/server/deprecations.ts @@ -0,0 +1,70 @@ +/* + * 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 { + CoreStart, + SavedObjectsClient, + Logger, + GetDeprecationsContext, + DeprecationsDetails, +} from 'src/core/server'; + +export const getTimelionSheetsCount = async ( + savedObjectsClient: Pick +) => { + const { total } = await savedObjectsClient.find({ type: 'timelion-sheet', perPage: 1 }); + return total; +}; + +export const showWarningMessageIfTimelionSheetWasFound = async ( + core: CoreStart, + logger: Logger +) => { + const { savedObjects } = core; + const savedObjectsClient = savedObjects.createInternalRepository(); + const count = await getTimelionSheetsCount(savedObjectsClient); + if (count > 0) { + logger.warn( + 'Deprecated since 7.0, the Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard. See https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html.' + ); + } +}; + +/** + * Deprecated since 7.0, the Timelion app will be removed in 8.0. + * To continue using your Timelion worksheets, migrate them to a dashboard. + * + * @link https://www.elastic.co/guide/en/kibana/master/timelion.html#timelion-deprecation + **/ +export async function getDeprecations({ + savedObjectsClient, +}: GetDeprecationsContext): Promise { + const deprecations: DeprecationsDetails[] = []; + const count = await getTimelionSheetsCount(savedObjectsClient); + + if (count > 0) { + deprecations.push({ + message: `You have ${count} Timelion worksheets. The Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard.`, + documentationUrl: + 'https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html', + level: 'warning', + correctiveActions: { + manualSteps: [ + 'Navigate to the Kibana Dashboard and click "Create dashboard".', + 'Select Timelion from the "New Visualization" window.', + 'Open a new tab, open the Timelion app, select the chart you want to copy, then copy the chart expression.', + 'Go to Timelion, paste the chart expression in the Timelion expression field, then click Update.', + 'In the toolbar, click Save.', + 'On the Save visualization window, enter the visualization Title, then click Save and return.', + ], + }, + }); + } + + return deprecations; +} diff --git a/src/plugins/timelion/server/plugin.ts b/src/plugins/timelion/server/plugin.ts index 226a978fe5d88..edbba9b565ae4 100644 --- a/src/plugins/timelion/server/plugin.ts +++ b/src/plugins/timelion/server/plugin.ts @@ -11,30 +11,7 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import { TimelionConfigType } from './config'; import { timelionSheetSavedObjectType } from './saved_objects'; - -/** - * Deprecated since 7.0, the Timelion app will be removed in 8.0. - * To continue using your Timelion worksheets, migrate them to a dashboard. - * - * @link https://www.elastic.co/guide/en/kibana/master/timelion.html#timelion-deprecation - **/ -const showWarningMessageIfTimelionSheetWasFound = (core: CoreStart, logger: Logger) => { - const { savedObjects } = core; - const savedObjectsClient = savedObjects.createInternalRepository(); - - savedObjectsClient - .find({ - type: 'timelion-sheet', - perPage: 1, - }) - .then( - ({ total }) => - total && - logger.warn( - 'Deprecated since 7.0, the Timelion app will be removed in 8.0. To continue using your Timelion worksheets, migrate them to a dashboard. See https://www.elastic.co/guide/en/kibana/current/create-panels-with-timelion.html.' - ) - ); -}; +import { getDeprecations, showWarningMessageIfTimelionSheetWasFound } from './deprecations'; export class TimelionPlugin implements Plugin { private logger: Logger; @@ -87,6 +64,8 @@ export class TimelionPlugin implements Plugin { schema: schema.number(), }, }); + + core.deprecations.registerDeprecations({ getDeprecations }); } start(core: CoreStart) { showWarningMessageIfTimelionSheetWasFound(core, this.logger); diff --git a/src/plugins/vis_type_timelion/server/index.ts b/src/plugins/vis_type_timelion/server/index.ts index 1dcb7263c4818..35f4182a50a86 100644 --- a/src/plugins/vis_type_timelion/server/index.ts +++ b/src/plugins/vis_type_timelion/server/index.ts @@ -21,7 +21,7 @@ export const config: PluginConfigDescriptor = { renameFromRoot('timelion_vis.enabled', 'vis_type_timelion.enabled'), renameFromRoot('timelion.enabled', 'vis_type_timelion.enabled'), renameFromRoot('timelion.graphiteUrls', 'vis_type_timelion.graphiteUrls'), - renameFromRoot('timelion.ui.enabled', 'vis_type_timelion.ui.enabled', true), + renameFromRoot('timelion.ui.enabled', 'vis_type_timelion.ui.enabled', { silent: true }), ], }; export const plugin = (initializerContext: PluginInitializerContext) => diff --git a/src/plugins/vis_type_timeseries/server/index.ts b/src/plugins/vis_type_timeseries/server/index.ts index 37eda1b1338d4..3c27713c37500 100644 --- a/src/plugins/vis_type_timeseries/server/index.ts +++ b/src/plugins/vis_type_timeseries/server/index.ts @@ -15,9 +15,13 @@ export { VisTypeTimeseriesSetup } from './plugin'; export const config: PluginConfigDescriptor = { deprecations: ({ unused, renameFromRoot }) => [ // In Kibana v7.8 plugin id was renamed from 'metrics' to 'vis_type_timeseries': - renameFromRoot('metrics.enabled', 'vis_type_timeseries.enabled', true), - renameFromRoot('metrics.chartResolution', 'vis_type_timeseries.chartResolution', true), - renameFromRoot('metrics.minimumBucketSize', 'vis_type_timeseries.minimumBucketSize', true), + renameFromRoot('metrics.enabled', 'vis_type_timeseries.enabled', { silent: true }), + renameFromRoot('metrics.chartResolution', 'vis_type_timeseries.chartResolution', { + silent: true, + }), + renameFromRoot('metrics.minimumBucketSize', 'vis_type_timeseries.minimumBucketSize', { + silent: true, + }), // Unused properties which should be removed after releasing Kibana v8.0: unused('chartResolution'), diff --git a/test/functional/fixtures/es_archiver/deprecations_service/data.json b/test/functional/fixtures/es_archiver/deprecations_service/data.json new file mode 100644 index 0000000000000..31ce5af20b46c --- /dev/null +++ b/test/functional/fixtures/es_archiver/deprecations_service/data.json @@ -0,0 +1,14 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "test-deprecations-plugin:ff3733a0-9fty-11e7-ahb3-3dcb94193fab", + "source": { + "type": "test-deprecations-plugin", + "updated_at": "2021-02-11T18:51:23.794Z", + "test-deprecations-plugin": { + "title": "Test saved object" + } + } + } +} diff --git a/test/functional/fixtures/es_archiver/deprecations_service/mappings.json b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json new file mode 100644 index 0000000000000..5f7c7e0e7b7dc --- /dev/null +++ b/test/functional/fixtures/es_archiver/deprecations_service/mappings.json @@ -0,0 +1,289 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "mappings": { + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "dynamic": "strict", + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "dynamic": "strict", + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "url": { + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "query": { + "properties": { + "title": { + "type": "text" + }, + "description": { + "type": "text" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "type": "keyword", + "index": false + } + } + }, + "filters": { + "type": "object", + "enabled": false + }, + "timefilter": { + "type": "object", + "enabled": false + } + } + }, + "test-deprecations-plugin": { + "properties": { + "title": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts index fc747fcd71f17..1651e213ee82d 100644 --- a/test/plugin_functional/config.ts +++ b/test/plugin_functional/config.ts @@ -56,6 +56,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { // Required to load new platform plugins via `--plugin-path` flag. '--env.name=development', + '--corePluginDeprecations.oldProperty=hello', + '--corePluginDeprecations.secret=100', + '--corePluginDeprecations.noLongerUsed=still_using', ...plugins.map( (pluginDir) => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}` ), diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/kibana.json b/test/plugin_functional/plugins/core_plugin_deprecations/kibana.json new file mode 100644 index 0000000000000..bc251f97bea58 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "corePluginDeprecations", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["corePluginDeprecations"], + "server": true, + "ui": false +} diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/package.json b/test/plugin_functional/plugins/core_plugin_deprecations/package.json new file mode 100644 index 0000000000000..f14ec933f59b2 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/package.json @@ -0,0 +1,14 @@ +{ + "name": "core_plugin_deprecations", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/core_plugin_deprecations", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "SSPL-1.0 OR Elastic License 2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && ../../../../node_modules/.bin/tsc" + } +} diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/public/application.tsx b/test/plugin_functional/plugins/core_plugin_deprecations/public/application.tsx new file mode 100644 index 0000000000000..e2166a249e34b --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/public/application.tsx @@ -0,0 +1,19 @@ +/* + * 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 React from 'react'; +import ReactDOM from 'react-dom'; +import { AppMountParameters } from 'kibana/public'; + +const DeprecationsApp = () =>

Deprcations App
; + +export const renderApp = ({ element }: AppMountParameters) => { + ReactDOM.render(, element); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/public/index.ts b/test/plugin_functional/plugins/core_plugin_deprecations/public/index.ts new file mode 100644 index 0000000000000..bb6b3f0740b3b --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/public/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { PluginInitializer, PluginInitializerContext } from 'kibana/public'; +import { + CorePluginDeprecationsPlugin, + CorePluginDeprecationsPluginSetup, + CorePluginDeprecationsPluginStart, +} from './plugin'; + +export const plugin: PluginInitializer< + CorePluginDeprecationsPluginSetup, + CorePluginDeprecationsPluginStart +> = (context: PluginInitializerContext) => new CorePluginDeprecationsPlugin(context); diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_deprecations/public/plugin.tsx new file mode 100644 index 0000000000000..bf807145e14bf --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/public/plugin.tsx @@ -0,0 +1,40 @@ +/* + * 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 { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/public'; + +declare global { + interface Window { + env?: PluginInitializerContext['env']; + } +} + +export class CorePluginDeprecationsPlugin + implements Plugin { + constructor(pluginContext: PluginInitializerContext) { + window.env = pluginContext.env; + } + public setup(core: CoreSetup) { + core.application.register({ + id: 'core-plugin-deprecations', + title: 'Core Plugin Deprecations', + async mount(params) { + const { renderApp } = await import('./application'); + await core.getStartServices(); + return renderApp(params); + }, + }); + } + + public start() {} + + public stop() {} +} + +export type CorePluginDeprecationsPluginSetup = ReturnType; +export type CorePluginDeprecationsPluginStart = ReturnType; diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts new file mode 100644 index 0000000000000..db4288d26a3d7 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/config.ts @@ -0,0 +1,41 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; +import { get } from 'lodash'; +import type { PluginConfigDescriptor } from 'kibana/server'; +import type { ConfigDeprecation } from '@kbn/config'; + +const configSchema = schema.object({ + newProperty: schema.maybe(schema.string({ defaultValue: 'Some string' })), + noLongerUsed: schema.maybe(schema.string()), + secret: schema.maybe(schema.number({ defaultValue: 42 })), +}); + +type ConfigType = TypeOf; + +const configSecretDeprecation: ConfigDeprecation = (settings, fromPath, addDeprecation) => { + if (get(settings, 'corePluginDeprecations.secret') !== 42) { + addDeprecation({ + documentationUrl: 'config-secret-doc-url', + message: + 'Kibana plugin funcitonal tests will no longer allow corePluginDeprecations.secret ' + + 'config to be set to anything except 42.', + }); + } + return settings; +}; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: ({ rename, unused }) => [ + rename('oldProperty', 'newProperty'), + unused('noLongerUsed'), + configSecretDeprecation, + ], +}; diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/index.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/index.ts new file mode 100644 index 0000000000000..1968c011a327a --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { CorePluginDeprecationsPlugin } from './plugin'; + +export { config } from './config'; +export const plugin = () => new CorePluginDeprecationsPlugin(); diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts new file mode 100644 index 0000000000000..38565b1e2c0a8 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/plugin.ts @@ -0,0 +1,57 @@ +/* + * 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 { Plugin, CoreSetup, GetDeprecationsContext, DeprecationsDetails } from 'kibana/server'; +import { registerRoutes } from './routes'; +async function getDeprecations({ + savedObjectsClient, +}: GetDeprecationsContext): Promise { + const deprecations: DeprecationsDetails[] = []; + const { total } = await savedObjectsClient.find({ type: 'test-deprecations-plugin', perPage: 1 }); + + deprecations.push({ + message: `CorePluginDeprecationsPlugin is a deprecated feature for testing.`, + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + manualSteps: ['Step a', 'Step b'], + }, + }); + + if (total > 0) { + deprecations.push({ + message: `SavedObject test-deprecations-plugin is still being used.`, + documentationUrl: 'another-test-url', + level: 'critical', + correctiveActions: {}, + }); + } + + return deprecations; +} + +export class CorePluginDeprecationsPlugin implements Plugin { + public setup(core: CoreSetup, deps: {}) { + registerRoutes(core.http); + core.savedObjects.registerType({ + name: 'test-deprecations-plugin', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + }); + + core.deprecations.registerDeprecations({ getDeprecations }); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/server/routes.ts b/test/plugin_functional/plugins/core_plugin_deprecations/server/routes.ts new file mode 100644 index 0000000000000..d6bf065898f93 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/server/routes.ts @@ -0,0 +1,45 @@ +/* + * 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 { HttpServiceSetup } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + +export function registerRoutes(http: HttpServiceSetup) { + const router = http.createRouter(); + router.post( + { + path: '/api/core_deprecations_resolve/', + validate: { + body: schema.object({ + mockFail: schema.maybe(schema.boolean()), + keyId: schema.maybe(schema.string()), + deprecationDetails: schema.object({ + domainId: schema.string(), + }), + }), + }, + }, + async (context, req, res) => { + const { mockFail, keyId } = req.body; + if (mockFail === true) { + return res.badRequest({ + body: new Error('Mocking api failure'), + }); + } + + if (keyId) { + const client = context.core.savedObjects.getClient(); + await client.delete('test-deprecations-plugin', keyId, { + refresh: true, + }); + } + + return res.ok(); + } + ); +} diff --git a/test/plugin_functional/plugins/core_plugin_deprecations/tsconfig.json b/test/plugin_functional/plugins/core_plugin_deprecations/tsconfig.json new file mode 100644 index 0000000000000..3d9d8ca9451d4 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_deprecations/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [], + "references": [ + { "path": "../../../../src/core/tsconfig.json" } + ] +} diff --git a/test/plugin_functional/test_suites/core/deprecations.ts b/test/plugin_functional/test_suites/core/deprecations.ts new file mode 100644 index 0000000000000..c44781ab284c6 --- /dev/null +++ b/test/plugin_functional/test_suites/core/deprecations.ts @@ -0,0 +1,247 @@ +/* + * 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 expect from '@kbn/expect'; +import type { DomainDeprecationDetails, DeprecationsGetResponse } from 'src/core/server/types'; +import type { ResolveDeprecationResponse } from 'src/core/public'; +import { PluginFunctionalProviderContext } from '../../services'; + +export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + + const CorePluginDeprecationsPluginDeprecations = [ + { + level: 'critical', + message: + '"corePluginDeprecations.oldProperty" is deprecated and has been replaced by "corePluginDeprecations.newProperty"', + correctiveActions: { + manualSteps: [ + 'Replace "corePluginDeprecations.oldProperty" with "corePluginDeprecations.newProperty" in the Kibana config file, CLI flag, or environment variable (in Docker only).', + ], + }, + domainId: 'corePluginDeprecations', + }, + { + level: 'critical', + message: 'corePluginDeprecations.noLongerUsed is deprecated and is no longer used', + correctiveActions: { + manualSteps: [ + 'Remove "corePluginDeprecations.noLongerUsed" from the Kibana config file, CLI flag, or environment variable (in Docker only)', + ], + }, + domainId: 'corePluginDeprecations', + }, + { + level: 'critical', + message: + 'Kibana plugin funcitonal tests will no longer allow corePluginDeprecations.secret config to be set to anything except 42.', + correctiveActions: {}, + documentationUrl: 'config-secret-doc-url', + domainId: 'corePluginDeprecations', + }, + { + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + manualSteps: ['Step a', 'Step b'], + }, + domainId: 'corePluginDeprecations', + }, + { + message: 'SavedObject test-deprecations-plugin is still being used.', + documentationUrl: 'another-test-url', + level: 'critical', + correctiveActions: {}, + domainId: 'corePluginDeprecations', + }, + ]; + + describe('deprecations service', () => { + before(() => esArchiver.load('../functional/fixtures/es_archiver/deprecations_service')); + after(() => esArchiver.unload('../functional/fixtures/es_archiver/deprecations_service')); + + describe('GET /api/deprecations/', async () => { + it('returns registered config deprecations and feature deprecations', async () => { + const { body } = await supertest.get('/api/deprecations/').set('kbn-xsrf', 'true'); + + const { deprecations } = body as DeprecationsGetResponse; + expect(Array.isArray(deprecations)).to.be(true); + const corePluginDeprecations = deprecations.filter( + ({ domainId }) => domainId === 'corePluginDeprecations' + ); + + expect(corePluginDeprecations).to.eql(CorePluginDeprecationsPluginDeprecations); + }); + }); + + describe('Public API', () => { + before(async () => await PageObjects.common.navigateToApp('home')); + + it('#getAllDeprecations returns all deprecations plugin deprecations', async () => { + const result = await browser.executeAsync((cb) => { + return window._coreProvider.start.core.deprecations.getAllDeprecations().then(cb); + }); + + const corePluginDeprecations = result.filter( + ({ domainId }) => domainId === 'corePluginDeprecations' + ); + + expect(corePluginDeprecations).to.eql(CorePluginDeprecationsPluginDeprecations); + }); + + it('#getDeprecations returns domain deprecations', async () => { + const corePluginDeprecations = await browser.executeAsync( + (cb) => { + return window._coreProvider.start.core.deprecations + .getDeprecations('corePluginDeprecations') + .then(cb); + } + ); + + expect(corePluginDeprecations).to.eql(CorePluginDeprecationsPluginDeprecations); + }); + + describe('resolveDeprecation', () => { + it('fails on missing correctiveActions.api', async () => { + const resolveResult = await browser.executeAsync((cb) => { + return window._coreProvider.start.core.deprecations + .resolveDeprecation({ + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + manualSteps: ['Step a', 'Step b'], + }, + domainId: 'corePluginDeprecations', + }) + .then(cb); + }); + + expect(resolveResult).to.eql({ + reason: 'deprecation has no correctiveAction via api.', + status: 'fail', + }); + }); + + it('fails on bad request from correctiveActions.api', async () => { + const resolveResult = await browser.executeAsync((cb) => { + return window._coreProvider.start.core.deprecations + .resolveDeprecation({ + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + api: { + method: 'POST', + path: '/api/core_deprecations_resolve/', + body: { + mockFail: true, + }, + }, + }, + domainId: 'corePluginDeprecations', + }) + .then(cb); + }); + + expect(resolveResult).to.eql({ + reason: 'Mocking api failure', + status: 'fail', + }); + }); + + it('fails on 404 request from correctiveActions.api', async () => { + const resolveResult = await browser.executeAsync((cb) => { + return window._coreProvider.start.core.deprecations + .resolveDeprecation({ + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + api: { + method: 'POST', + path: '/api/invalid_route_not_registered/', + body: { + mockFail: true, + }, + }, + }, + domainId: 'corePluginDeprecations', + }) + .then(cb); + }); + + expect(resolveResult).to.eql({ + reason: 'Not Found', + status: 'fail', + }); + }); + + it('returns { status: ok } on successful correctiveActions.api', async () => { + const savedObjectId = await supertest + .get('/api/saved_objects/_find?type=test-deprecations-plugin') + .set('kbn-xsrf', 'true') + .expect(200) + .then(({ body }) => { + expect(body.total).to.be(1); + return body.saved_objects[0].id; + }); + + const resolveResult = await browser.executeAsync( + (keyId, cb) => { + return window._coreProvider.start.core.deprecations + .resolveDeprecation({ + message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', + documentationUrl: 'test-url', + level: 'warning', + correctiveActions: { + api: { + method: 'POST', + path: '/api/core_deprecations_resolve/', + body: { keyId }, + }, + }, + domainId: 'corePluginDeprecations', + }) + .then(cb); + }, + savedObjectId + ); + + expect(resolveResult).to.eql({ status: 'ok' }); + await supertest + .get('/api/saved_objects/_find?type=test-deprecations-plugin') + .set('kbn-xsrf', 'true') + .expect(200) + .then(({ body }) => { + expect(body.total).to.be(0); + }); + + const { deprecations } = await supertest + .get('/api/deprecations/') + .set('kbn-xsrf', 'true') + .then( + ({ body }): Promise => { + return body; + } + ); + + const deprecation = deprecations.find( + ({ message }) => message === 'SavedObject test-deprecations-plugin is still being used.' + ); + + expect(deprecation).to.eql(undefined); + }); + }); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core/index.ts b/test/plugin_functional/test_suites/core/index.ts index 9baa1ab0b394d..8591c2fdec8dd 100644 --- a/test/plugin_functional/test_suites/core/index.ts +++ b/test/plugin_functional/test_suites/core/index.ts @@ -10,6 +10,7 @@ import { PluginFunctionalProviderContext } from '../../services'; export default function ({ loadTestFile }: PluginFunctionalProviderContext) { describe('core', function () { + loadTestFile(require.resolve('./deprecations')); loadTestFile(require.resolve('./route')); }); } diff --git a/x-pack/plugins/monitoring/server/deprecations.test.js b/x-pack/plugins/monitoring/server/deprecations.test.js index d7e1a2340d295..2931f704a4478 100644 --- a/x-pack/plugins/monitoring/server/deprecations.test.js +++ b/x-pack/plugins/monitoring/server/deprecations.test.js @@ -16,8 +16,8 @@ describe('monitoring plugin deprecations', function () { beforeAll(function () { const deprecations = deprecationsModule({ rename, renameFromRoot }); - transformDeprecations = (settings, fromPath, log = noop) => { - deprecations.forEach((deprecation) => deprecation(settings, fromPath, log)); + transformDeprecations = (settings, fromPath, addDeprecation = noop) => { + deprecations.forEach((deprecation) => deprecation(settings, fromPath, addDeprecation)); }; }); @@ -31,9 +31,9 @@ describe('monitoring plugin deprecations', function () { }, }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); it(`shouldn't log when email_address is specified`, function () { @@ -46,9 +46,9 @@ describe('monitoring plugin deprecations', function () { }, }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); it(`should log when email_address is missing, but alerts/notifications are both enabled`, function () { @@ -60,9 +60,9 @@ describe('monitoring plugin deprecations', function () { }, }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); }); @@ -70,65 +70,65 @@ describe('monitoring plugin deprecations', function () { it('logs a warning if elasticsearch.username is set to "elastic"', () => { const settings = { elasticsearch: { username: 'elastic' } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); it('logs a warning if elasticsearch.username is set to "kibana"', () => { const settings = { elasticsearch: { username: 'kibana' } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); it('does not log a warning if elasticsearch.username is set to something besides "elastic" or "kibana"', () => { const settings = { elasticsearch: { username: 'otheruser' } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); it('does not log a warning if elasticsearch.username is unset', () => { const settings = { elasticsearch: { username: undefined } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); it('logs a warning if ssl.key is set and ssl.certificate is not', () => { const settings = { elasticsearch: { ssl: { key: '' } } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); it('logs a warning if ssl.certificate is set and ssl.key is not', () => { const settings = { elasticsearch: { ssl: { certificate: '' } } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).toHaveBeenCalled(); }); it('does not log a warning if both ssl.key and ssl.certificate are set', () => { const settings = { elasticsearch: { ssl: { key: '', certificate: '' } } }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); - expect(log).not.toHaveBeenCalled(); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); + expect(addDeprecation).not.toHaveBeenCalled(); }); }); describe('xpack_api_polling_frequency_millis', () => { it('should call rename for this renamed config key', () => { const settings = { xpack_api_polling_frequency_millis: 30000 }; - const log = jest.fn(); - transformDeprecations(settings, fromPath, log); + const addDeprecation = jest.fn(); + transformDeprecations(settings, fromPath, addDeprecation); expect(rename).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/monitoring/server/deprecations.ts b/x-pack/plugins/monitoring/server/deprecations.ts index a276cfcee0d35..79b879b3a5f8b 100644 --- a/x-pack/plugins/monitoring/server/deprecations.ts +++ b/x-pack/plugins/monitoring/server/deprecations.ts @@ -44,41 +44,41 @@ export const deprecations = ({ 'monitoring.ui.elasticsearch.logFetchCount' ), renameFromRoot('xpack.monitoring', 'monitoring'), - (config, fromPath, logger) => { + (config, fromPath, addDeprecation) => { const emailNotificationsEnabled = get(config, 'cluster_alerts.email_notifications.enabled'); if (emailNotificationsEnabled && !get(config, CLUSTER_ALERTS_ADDRESS_CONFIG_KEY)) { - logger( - `Config key [${fromPath}.${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}] will be required for email notifications to work in 7.0."` - ); + addDeprecation({ + message: `Config key [${fromPath}.${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}] will be required for email notifications to work in 7.0."`, + }); } return config; }, - (config, fromPath, logger) => { + (config, fromPath, addDeprecation) => { const es: Record = get(config, 'elasticsearch'); if (es) { if (es.username === 'elastic') { - logger( - `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana_system" user instead.` - ); + addDeprecation({ + message: `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana_system" user instead.`, + }); } else if (es.username === 'kibana') { - logger( - `Setting [${fromPath}.username] to "kibana" is deprecated. You should use the "kibana_system" user instead.` - ); + addDeprecation({ + message: `Setting [${fromPath}.username] to "kibana" is deprecated. You should use the "kibana_system" user instead.`, + }); } } return config; }, - (config, fromPath, logger) => { + (config, fromPath, addDeprecation) => { const ssl: Record = get(config, 'elasticsearch.ssl'); if (ssl) { if (ssl.key !== undefined && ssl.certificate === undefined) { - logger( - `Setting [${fromPath}.key] without [${fromPath}.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` - ); + addDeprecation({ + message: `Setting [${fromPath}.key] without [${fromPath}.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`, + }); } else if (ssl.certificate !== undefined && ssl.key === undefined) { - logger( - `Setting [${fromPath}.certificate] without [${fromPath}.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` - ); + addDeprecation({ + message: `Setting [${fromPath}.certificate] without [${fromPath}.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.`, + }); } } return config; diff --git a/x-pack/plugins/reporting/server/config/index.test.ts b/x-pack/plugins/reporting/server/config/index.test.ts index d7c12937cda7c..a395cd23288eb 100644 --- a/x-pack/plugins/reporting/server/config/index.test.ts +++ b/x-pack/plugins/reporting/server/config/index.test.ts @@ -21,7 +21,7 @@ const applyReportingDeprecations = (settings: Record = {}) => { deprecation, path: CONFIG_PATH, })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index 06975aa85f1e4..4b97dbc1e2a84 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -24,12 +24,12 @@ export const config: PluginConfigDescriptor = { unused('poll.jobCompletionNotifier.intervalErrorMultiplier'), unused('poll.jobsRefresh.intervalErrorMultiplier'), unused('kibanaApp'), - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const reporting = get(settings, fromPath); if (reporting?.index) { - log( - `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details` - ); + addDeprecation({ + message: `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details`, + }); } return settings; }, diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts index 2b6ad603e6163..80173dd42a49e 100644 --- a/x-pack/plugins/security/server/config_deprecations.test.ts +++ b/x-pack/plugins/security/server/config_deprecations.test.ts @@ -20,7 +20,7 @@ const applyConfigDeprecations = (settings: Record = {}) => { deprecation, path: 'xpack.security', })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/security/server/config_deprecations.ts b/x-pack/plugins/security/server/config_deprecations.ts index a7bb5e09fb919..eae996fe2a5c0 100644 --- a/x-pack/plugins/security/server/config_deprecations.ts +++ b/x-pack/plugins/security/server/config_deprecations.ts @@ -22,16 +22,17 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ unused('authorization.legacyFallback.enabled'), unused('authc.saml.maxRedirectURLSize'), // Deprecation warning for the old array-based format of `xpack.security.authc.providers`. - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { if (Array.isArray(settings?.xpack?.security?.authc?.providers)) { - log( - 'Defining `xpack.security.authc.providers` as an array of provider types is deprecated. Use extended `object` format instead.' - ); + addDeprecation({ + message: + 'Defining `xpack.security.authc.providers` as an array of provider types is deprecated. Use extended `object` format instead.', + }); } return settings; }, - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const hasProviderType = (providerType: string) => { const providers = settings?.xpack?.security?.authc?.providers; if (Array.isArray(providers)) { @@ -44,31 +45,34 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ }; if (hasProviderType('basic') && hasProviderType('token')) { - log( - 'Enabling both `basic` and `token` authentication providers in `xpack.security.authc.providers` is deprecated. Login page will only use `token` provider.' - ); + addDeprecation({ + message: + 'Enabling both `basic` and `token` authentication providers in `xpack.security.authc.providers` is deprecated. Login page will only use `token` provider.', + }); } return settings; }, - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const samlProviders = (settings?.xpack?.security?.authc?.providers?.saml ?? {}) as Record< string, any >; if (Object.values(samlProviders).find((provider) => !!provider.maxRedirectURLSize)) { - log( - '`xpack.security.authc.providers.saml..maxRedirectURLSize` is deprecated and is no longer used' - ); + addDeprecation({ + message: + '`xpack.security.authc.providers.saml..maxRedirectURLSize` is deprecated and is no longer used', + }); } return settings; }, - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { if (settings?.xpack?.security?.enabled === false) { - log( - 'Disabling the security plugin (`xpack.security.enabled`) will not be supported in the next major version (8.0). ' + - 'To turn off security features, disable them in Elasticsearch instead.' - ); + addDeprecation({ + message: + 'Disabling the security plugin (`xpack.security.enabled`) will not be supported in the next major version (8.0). ' + + 'To turn off security features, disable them in Elasticsearch instead.', + }); } return settings; }, diff --git a/x-pack/plugins/spaces/server/config.test.ts b/x-pack/plugins/spaces/server/config.test.ts index 41c4995b5bcf3..1ce1be0698b1c 100644 --- a/x-pack/plugins/spaces/server/config.test.ts +++ b/x-pack/plugins/spaces/server/config.test.ts @@ -19,7 +19,7 @@ const applyConfigDeprecations = (settings: Record = {}) => { deprecation, path: '', })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/spaces/server/config.ts b/x-pack/plugins/spaces/server/config.ts index bc53f43c3e8c4..ed541fda6c292 100644 --- a/x-pack/plugins/spaces/server/config.ts +++ b/x-pack/plugins/spaces/server/config.ts @@ -24,11 +24,11 @@ export function createConfig$(context: PluginInitializerContext) { return context.config.create>(); } -const disabledDeprecation: ConfigDeprecation = (config, fromPath, log) => { +const disabledDeprecation: ConfigDeprecation = (config, fromPath, addDeprecation) => { if (config.xpack?.spaces?.enabled === false) { - log( - `Disabling the spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)` - ); + addDeprecation({ + message: `Disabling the spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)`, + }); } return config; }; diff --git a/x-pack/plugins/task_manager/server/index.test.ts b/x-pack/plugins/task_manager/server/index.test.ts index 470b47b40f67d..3fce5f7bdfdf4 100644 --- a/x-pack/plugins/task_manager/server/index.test.ts +++ b/x-pack/plugins/task_manager/server/index.test.ts @@ -22,7 +22,7 @@ const applyTaskManagerDeprecations = (settings: Record = {}) => deprecation, path: CONFIG_PATH, })), - (msg) => deprecationMessages.push(msg) + () => ({ message }) => deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts index 6d744010757f5..a34f5a87fddbe 100644 --- a/x-pack/plugins/task_manager/server/index.ts +++ b/x-pack/plugins/task_manager/server/index.ts @@ -31,17 +31,18 @@ export { export const config: PluginConfigDescriptor = { schema: configSchema, deprecations: () => [ - (settings, fromPath, log) => { + (settings, fromPath, addDeprecation) => { const taskManager = get(settings, fromPath); if (taskManager?.index) { - log( - `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details` - ); + addDeprecation({ + documentationUrl: 'https://ela.st/kbn-remove-legacy-multitenancy', + message: `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details`, + }); } if (taskManager?.max_workers > MAX_WORKERS_LIMIT) { - log( - `setting "${fromPath}.max_workers" (${taskManager?.max_workers}) greater than ${MAX_WORKERS_LIMIT} is deprecated. Values greater than ${MAX_WORKERS_LIMIT} will not be supported starting in 8.0.` - ); + addDeprecation({ + message: `setting "${fromPath}.max_workers" (${taskManager?.max_workers}) greater than ${MAX_WORKERS_LIMIT} is deprecated. Values greater than ${MAX_WORKERS_LIMIT} will not be supported starting in 8.0.`, + }); } return settings; },